Merge remote-tracking branch 'origin/rel-1.4.0'
Conflicts: docs/overview.rst pom.xml
25
Jenkinsfile
vendored
|
@ -22,27 +22,20 @@ pipeline {
|
|||
|
||||
stage('Reporting') {
|
||||
steps {
|
||||
pmd canComputeNew: false, defaultEncoding: '', healthy: '', pattern: '**/target/pmd.xml', thresholdLimit: 'high', unHealthy: ''
|
||||
findbugs canComputeNew: false, defaultEncoding: '', excludePattern: '', healthy: '', includePattern: '', isRankActivated: true, pattern: '**/target/findbugsXml.xml', unHealthy: ''
|
||||
jacoco classPattern: '**/build/classes/*/main/', execPattern: '**/target/*.exec', sourcePattern: '**/src/main/java', inclusionPattern: '**/*.class'
|
||||
withMaven(maven: 'Maven', options: [findbugsPublisher(disabled: true)]) {
|
||||
sh "mvn --batch-mode -V -U -e -P let_reporting pmd:pmd pmd:cpd findbugs:findbugs spotbugs:spotbugs"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Tag') {
|
||||
steps {
|
||||
echo 'Build is tagged here.'
|
||||
}
|
||||
}
|
||||
|
||||
stage('Push to Nexus') {
|
||||
steps {
|
||||
echo 'Build is pushed to Nexus here.'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
post {
|
||||
always {
|
||||
junit testResults: '**/target/surefire-reports/TEST-*.xml'
|
||||
|
||||
recordIssues enabledForFailure: true, tool: spotBugs()
|
||||
recordIssues enabledForFailure: true, tool: pmdParser(pattern: '**/target/pmd.xml')
|
||||
}
|
||||
failure {
|
||||
setBuildStatus("Build failed", "FAILURE");
|
||||
emailext body: "The build of the LET Application (${env.JOB_NAME}) failed! See ${env.BUILD_URL}", recipientProviders: [[$class: 'CulpritsRecipientProvider']], subject: 'LET Application Build Failure'
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
coverage:
|
||||
precision: 2
|
||||
round: down
|
||||
range: "40...100"
|
||||
range: "30..70"
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
|
|
|
@ -151,6 +151,29 @@ your institution use the type information of the exam to set them into context.
|
|||
- When you have selected a exam configuration the dialog shows you some additional information about the exam configuration.
|
||||
- If you want or need to put an password protected encryption to the exam configuration for this exam you can do so by give the password for the encryption also within the attachment dialog. Be aware that every SEB client that will receive an encrypted exam configuration from the SEB Server will prompt the user to give the correct password. In most cases an encryption of the exam configuration is not needed, because a secure HTTPS connection form SEB client to SEB Server is already in place.
|
||||
|
||||
**Archive an exam**
|
||||
|
||||
Since SEB Server version 1.4 it is possible to archive an exam that has been finished. An archived exam and all its data is still available
|
||||
on the SEB Server but read only and the exam is not been updated from the LMS data anymore and it is not possible to run this exam again.
|
||||
|
||||
This is a good use-case to organize your exams since archived exam are not shown in the Exam list with the default filter anymore. They are
|
||||
only shown if the status filter of the exam list is explicitly set to Archived status. An they are shown within the new "Finished Exam"
|
||||
section in the monitoring view.
|
||||
|
||||
.. image:: images/exam/archiveExamsFilter.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/exam/archiveExamsFilter.png
|
||||
|
||||
This is also a good use-case if you want to remove an LMS and LMS Setup but still want to be able to access the exams data on the SEB Server.
|
||||
In this case you can archive all exams from that LMS Setup before deactivating or deleting the respective LMS Setup.
|
||||
|
||||
To archive a finished exam you just have to use the "Archive Exam" action on the right action pane of the exam view:
|
||||
|
||||
.. image:: images/exam/archiveExam1.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/exam/archiveExam1.png
|
||||
|
||||
|
||||
**Delete an exam**
|
||||
|
||||
If you have "Exam Administrator" privileges you are able to entirely delete an existing exam and its dependencies.
|
||||
|
|
|
@ -56,6 +56,14 @@ And you are able to add/edit/remove monitoring indicators for the exam template
|
|||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/exam_template/indicator.png
|
||||
|
||||
There are also proctoring settings available since SEB Server version 1.4 for the exam template. They just have the same settings and
|
||||
look like the ones on the Exam and will get copied for an exam imported with the respective template that defines the proctoring settings.
|
||||
|
||||
.. image:: images/exam_template/proctoringSettings.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/exam_template/proctoringSettings.png
|
||||
|
||||
|
||||
Import Exam with Template
|
||||
-------------------------
|
||||
|
||||
|
|
BIN
docs/images/exam/archiveExam1.png
Normal file
After Width: | Height: | Size: 47 KiB |
BIN
docs/images/exam/archiveExamsFilter.png
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
docs/images/exam_config/batch-actions.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
docs/images/exam_config/batch-actions_statechange.png
Normal file
After Width: | Height: | Size: 64 KiB |
BIN
docs/images/exam_config/batch-actions_statechange_finished.png
Normal file
After Width: | Height: | Size: 60 KiB |
BIN
docs/images/exam_config/bulkActionSelection1.png
Normal file
After Width: | Height: | Size: 53 KiB |
BIN
docs/images/exam_config/bulkActionSelection2.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
docs/images/exam_config/bulkStateChange1.png
Normal file
After Width: | Height: | Size: 48 KiB |
BIN
docs/images/exam_config/bulkStateChange2.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
docs/images/exam_config/bulkStateChange3.png
Normal file
After Width: | Height: | Size: 43 KiB |
BIN
docs/images/exam_config/reset_to_template.png
Normal file
After Width: | Height: | Size: 56 KiB |
BIN
docs/images/exam_template/proctoringSettings.png
Normal file
After Width: | Height: | Size: 62 KiB |
BIN
docs/images/monitoring/finishedClientConnection.png
Normal file
After Width: | Height: | Size: 46 KiB |
BIN
docs/images/monitoring/finishedExam.png
Normal file
After Width: | Height: | Size: 66 KiB |
BIN
docs/images/monitoring/finishedExams.png
Normal file
After Width: | Height: | Size: 38 KiB |
BIN
docs/images/overview/list_multiselect.png
Normal file
After Width: | Height: | Size: 67 KiB |
BIN
docs/images/overview/list_multiselect_actions.png
Normal file
After Width: | Height: | Size: 70 KiB |
|
@ -187,6 +187,38 @@ A student as well as a proctor is then able to use all the features of the meeti
|
|||
- In both services while broadcasting, it is not guaranteed that a student always see the proctor. Usually the meeting service shows or pins the participant that is currently speaking automatically.
|
||||
|
||||
|
||||
Finished Exams
|
||||
--------------
|
||||
|
||||
Since SEB Server version 1.4 there is a new section "Finished Exams" within the monitoring section to view finished and archived exams
|
||||
like you do within the monitoring. You see all the SEB connections that has been connected to the exam when running and are able to view
|
||||
particular SEB client connection details by either double-click on a SEB client connection entry in the list or by selection and using the View action
|
||||
on the right action pane.
|
||||
|
||||
In the "Finished Exams" list you can see all finished or archived exams and filter the list by Name, State and Type.
|
||||
|
||||
.. image:: images/monitoring/finishedExams.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/monitoring/finishedExams.png
|
||||
|
||||
To see a particular finished or archived exam you can just double-click in the list entry or use the View action on the right action pane.
|
||||
In the exam view you see all SEB connections that has been connected to the exam during the exam run just like in the usual monitoring view
|
||||
but with no update since the SEB connections are not active and the data is not changing anymore. You are able to filter the list by
|
||||
User or Session Info, Connection Info or Status and are also be able to sort the list even for indicator columns.
|
||||
|
||||
.. image:: images/monitoring/finishedExam.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/monitoring/finishedExam.png
|
||||
|
||||
As in the usual monitoring view, you can show SEB client connection details by double-clicking on a list entry or by selecting a list entry
|
||||
and use the View action on the right action pane.
|
||||
In the detail view you see the same information for a particular SEB client connection as within the usual monitoring view. You can view
|
||||
the SEB client logs of a SEB client connection here and analyze it after the exam was running.
|
||||
|
||||
.. image:: images/monitoring/finishedClientConnection.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/monitoring/finishedClientConnection.png
|
||||
|
||||
|
||||
All SEB Client Logs
|
||||
-------------------
|
||||
|
|
|
@ -160,6 +160,20 @@ that do not have a sort functionality yet.
|
|||
Most columns have a short tool-tip description that pops up while the mouse pointer stays over the column header for a moment.
|
||||
A column tool-tip usually also explains how to use the column-related filter.
|
||||
|
||||
**List Multi-Selection**
|
||||
|
||||
Since SEB Server version 1.4, multi-selection for some lists with bulk-actions is possible. To select multiple rows in a table that allows multi-selection
|
||||
just click on the row as usual. If you then click on another (still not selected) row, this row get selected too. You can do this even over several pages.
|
||||
To deselect a selected row just click it again then it will be removed from the selection.
|
||||
|
||||
.. image:: images/overview/list_multiselect.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/overview/list_multiselect.png
|
||||
|
||||
.. note::
|
||||
Some actions on the right action pane are used only for single objects but also enabled on multi-selection. If you have multiple selections
|
||||
and use a single object action like "View", "Edit" or "Copy" for exmaple, then the system will take the fist selected object/row to work with.
|
||||
|
||||
**Forms**
|
||||
|
||||
Forms are used for domain entity specific data input or presentation, like HTML Forms usually do. Forms appear in three
|
||||
|
@ -191,3 +205,20 @@ After correcting the missing or wrong input and saving the form again, the SEB S
|
|||
.. note::
|
||||
If you navigate away from a form in edit mode, the GUI will inform you about possible data loss on this action and will prompt you to
|
||||
proceed or abort the action.
|
||||
|
||||
|
||||
**Actions**
|
||||
|
||||
Actions are usually placed on the right action pane of the application and belongs to the actual site or view. There are generally three types of actions:
|
||||
|
||||
- Form Actions that directly belongs to the actual view or object and either save, manipulate or create a new object.
|
||||
- List Action - Single Selection are actions on a list page that effects the selected list entry.
|
||||
- List Action - Multi Selection are actions that refer to the current multi selection on a list and apply for every selected item.
|
||||
|
||||
.. note::
|
||||
List action are disabled when nothing is selected from the list and get enabled as soon as one or more list items are selected.
|
||||
Actions that are considdered single selection actions, and are used with a multi selection on the list will only affect the first selected item in the list.
|
||||
|
||||
.. image:: images/overview/list_multiselect_actions.png
|
||||
:align: center
|
||||
:target: https://raw.githubusercontent.com/SafeExamBrowser/seb-server/master/docs/images/overview/list_multiselect_actions.png
|
||||
|
|
|
@ -6,7 +6,29 @@ There shall be at least a problem description, an optional explanation if needed
|
|||
|
||||
Please also have a look at `Open Issues <https://https://github.com/SafeExamBrowser/seb-server/issues>`_ and/or `Ongoing Discussions <https://github.com/SafeExamBrowser/seb-server/discussions>`_ on the Git-Hub page.
|
||||
|
||||
--------------------------------
|
||||
|
||||
- **Version** : 1.3.x
|
||||
|
||||
- **Domain** : Exam Monitoring
|
||||
|
||||
- **Problem** : SEB connections get lost and ping-times go up for already connected SEB clients
|
||||
|
||||
- **Explanation** : This issue is due to a access token used by SEB client to authenticate on SEB Server that lasts not longer the one hour since SEB Server version 1.3 and since SEB client has no new access token request implements yet.
|
||||
|
||||
- **Solution** : A workaround for SEB Server version 1.3.x is to make the access token expiry-date last long enough to minimize the possibility that the access token became invalid during a exam. We recommend to set it to 12 hours = 43200 seconds. Therefore please set the following SEB Server setup properties in the respective application-prod.properties configuration file of your SEB Server setup:
|
||||
|
||||
sebserver.webservice.api.admin.accessTokenValiditySeconds=43200
|
||||
sebserver.webservice.api.exam.accessTokenValiditySeconds=43200
|
||||
|
||||
In SEB Server version 1.4 this is already set as default again and we are currently working on new SEB client versions that also
|
||||
handle SEB Server communication token expiry by automatically requesting a new access token (with new lifetime) from SEB Server
|
||||
when an old access token is not valid any longer.
|
||||
|
||||
--------------------------------
|
||||
|
||||
|
||||
**Template**
|
||||
--------------------------------
|
||||
|
||||
- **Version** : 1.0.0
|
||||
|
|
35
pom.xml
|
@ -18,7 +18,7 @@
|
|||
<packaging>jar</packaging>
|
||||
|
||||
<properties>
|
||||
<sebserver-version>1.3.3</sebserver-version>
|
||||
<sebserver-version>1.4.0</sebserver-version>
|
||||
<build-version>${sebserver-version}</build-version>
|
||||
<revision>${sebserver-version}</revision>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
|
@ -125,7 +125,7 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<!-- <plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<version>3.0.4</version>
|
||||
|
@ -145,7 +145,27 @@
|
|||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugin>-->
|
||||
<plugin>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs-maven-plugin</artifactId>
|
||||
<version>4.5.3.0</version>
|
||||
<configuration>
|
||||
<effort>Max</effort>
|
||||
<failOnError>false</failOnError>
|
||||
<threshold>Low</threshold>
|
||||
<xmlOutput>true</xmlOutput>
|
||||
<excludeFilterFile>findbugs-excludes.xml</excludeFilterFile>
|
||||
</configuration>
|
||||
<dependencies>
|
||||
<!-- overwrite dependency on spotbugs if you want to specify the version of spotbugs -->
|
||||
<dependency>
|
||||
<groupId>com.github.spotbugs</groupId>
|
||||
<artifactId>spotbugs</artifactId>
|
||||
<version>4.6.0</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
|
@ -291,6 +311,15 @@
|
|||
<artifactId>spring-boot-starter-validation</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Springdoc-openapi -->
|
||||
<dependency>
|
||||
<groupId>org.springdoc</groupId>
|
||||
<artifactId>springdoc-openapi-ui</artifactId>
|
||||
<version>1.5.10</version>
|
||||
</dependency>
|
||||
|
||||
|
||||
|
||||
<!-- Flyway -->
|
||||
<dependency>
|
||||
<groupId>org.flywaydb</groupId>
|
||||
|
|
|
@ -8,7 +8,11 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gbl;
|
||||
|
||||
import java.text.Collator;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.eclipse.swt.graphics.RGB;
|
||||
import org.eclipse.swt.graphics.RGBA;
|
||||
|
@ -19,6 +23,7 @@ import org.springframework.core.ParameterizedTypeReference;
|
|||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.authorization.Privilege;
|
||||
|
||||
/** Global Constants used in SEB Server web-service as well as in web-gui component */
|
||||
|
@ -155,6 +160,23 @@ public final class Constants {
|
|||
public static final RGB BLACK_RGB = new RGB(0, 0, 0);
|
||||
public static final RGBA GREY_DISABLED = new RGBA(150, 150, 150, 50);
|
||||
|
||||
public static final Collator DEFAULT_ENGLISH_COLLATOR = Collator.getInstance(Locale.ENGLISH);
|
||||
|
||||
public static final List<EntityType> ENTITY_TYPE_HIRARCHIE = Arrays.asList(
|
||||
EntityType.INSTITUTION,
|
||||
EntityType.USER,
|
||||
EntityType.USER_ACTIVITY_LOG,
|
||||
EntityType.CERTIFICATE,
|
||||
EntityType.LMS_SETUP,
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
EntityType.EXAM_TEMPLATE,
|
||||
EntityType.EXAM,
|
||||
EntityType.INDICATOR,
|
||||
EntityType.EXAM_CONFIGURATION_MAP,
|
||||
EntityType.CONFIGURATION_NODE,
|
||||
EntityType.CLIENT_CONNECTION,
|
||||
EntityType.CLIENT_EVENT);
|
||||
|
||||
public static final String IMPORTED_PASSWORD_MARKER = "_IMPORTED_PASSWORD";
|
||||
|
||||
public static final TypeReference<Collection<APIMessage>> TYPE_REFERENCE_API_MESSAGE =
|
||||
|
|
|
@ -20,7 +20,7 @@ public final class API {
|
|||
|
||||
public enum BatchActionType {
|
||||
EXAM_CONFIG_STATE_CHANGE(EntityType.CONFIGURATION_NODE),
|
||||
EXAM_CONFIG_APPLY_TEMPLATE_VALUES(EntityType.CONFIGURATION_NODE);
|
||||
EXAM_CONFIG_REST_TEMPLATE_SETTINGS(EntityType.CONFIGURATION_NODE);
|
||||
|
||||
public final EntityType entityType;
|
||||
|
||||
|
@ -75,7 +75,7 @@ public final class API {
|
|||
public static final String LIST_PATH_SEGMENT = "/list";
|
||||
|
||||
public static final String ACTIVE_PATH_SEGMENT = "/active";
|
||||
|
||||
public static final String TOGGLE_ACTIVITY_PATH_SEGMENT = "/toggle-activity";
|
||||
public static final String INACTIVE_PATH_SEGMENT = "/inactive";
|
||||
|
||||
public static final String DEPENDENCY_PATH_SEGMENT = "/dependency";
|
||||
|
@ -145,6 +145,7 @@ public final class API {
|
|||
|
||||
public static final String EXAM_ADMINISTRATION_ENDPOINT = "/exam";
|
||||
//public static final String EXAM_ADMINISTRATION_DOWNLOAD_CONFIG_PATH_SEGMENT = "/download-config";
|
||||
public static final String EXAM_ADMINISTRATION_ARCHIVE_PATH_SEGMENT = "/archive";
|
||||
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_PATH_SEGMENT = "/check-consistency";
|
||||
public static final String EXAM_ADMINISTRATION_CONSISTENCY_CHECK_INCLUDE_RESTRICTION = "include-restriction";
|
||||
public static final String EXAM_ADMINISTRATION_SEB_RESTRICTION_PATH_SEGMENT = "/seb-restriction";
|
||||
|
@ -156,6 +157,7 @@ public final class API {
|
|||
public static final String EXAM_INDICATOR_ENDPOINT = "/indicator";
|
||||
|
||||
public static final String SEB_CLIENT_CONFIG_ENDPOINT = "/client_configuration";
|
||||
public static final String SEB_CLIENT_CONFIG_CREDENTIALS_PATH_SEGMENT = "/credentials";
|
||||
public static final String SEB_CLIENT_CONFIG_DOWNLOAD_PATH_SEGMENT = "/download";
|
||||
|
||||
public static final String CONFIGURATION_NODE_ENDPOINT = "/configuration-node";
|
||||
|
@ -167,6 +169,7 @@ public final class API {
|
|||
public static final String CONFIGURATION_UNDO_PATH_SEGMENT = "/undo";
|
||||
public static final String CONFIGURATION_COPY_PATH_SEGMENT = "/copy";
|
||||
public static final String CONFIGURATION_RESTORE_FROM_HISTORY_PATH_SEGMENT = "/restore";
|
||||
public static final String CONFIGURATION_RESET_TO_TEMPLATE_PATH_SEGMENT = "/reset-to-template";
|
||||
public static final String CONFIGURATION_VALUE_ENDPOINT = "/configuration_value";
|
||||
public static final String CONFIGURATION_TABLE_VALUE_PATH_SEGMENT = "/table";
|
||||
public static final String CONFIGURATION_ATTRIBUTE_ENDPOINT = "/configuration_attribute";
|
||||
|
@ -193,6 +196,7 @@ public final class API {
|
|||
public static final String EXAM_MONITORING_NOTIFICATION_ENDPOINT = "/notification";
|
||||
public static final String EXAM_MONITORING_DISABLE_CONNECTION_ENDPOINT = "/disable-connection";
|
||||
public static final String EXAM_MONITORING_STATE_FILTER = "hidden-states";
|
||||
public static final String EXAM_MONITORING_FINISHED_ENDPOINT = "/finishedexams";
|
||||
public static final String EXAM_MONITORING_SEB_CONNECTION_TOKEN_PATH_SEGMENT =
|
||||
"/{" + EXAM_API_SEB_CONNECTION_TOKEN + "}";
|
||||
|
||||
|
@ -212,6 +216,7 @@ public final class API {
|
|||
public static final String EXAM_PROCTORING_ATTR_ALLOW_CHAT = "allow_chat";
|
||||
|
||||
public static final String SEB_CLIENT_CONNECTION_ENDPOINT = "/seb-client-connection";
|
||||
public static final String SEB_CLIENT_CONNECTION_DATA_ENDPOINT = "/data";
|
||||
|
||||
public static final String SEB_CLIENT_EVENT_ENDPOINT = "/seb-client-event";
|
||||
public static final String SEB_CLIENT_EVENT_SEARCH_PATH_SEGMENT = "/search";
|
||||
|
@ -230,4 +235,6 @@ public final class API {
|
|||
public static final String EXAM_TEMPLATE_INDICATOR_PATH_SEGMENT = "/indicator";
|
||||
public static final String EXAM_TEMPLATE_DEFAULT_PATH_SEGMENT = "/default";
|
||||
|
||||
public static final String BATCH_ACTION_ENDPOINT = "/batch-action";
|
||||
|
||||
}
|
||||
|
|
|
@ -246,7 +246,7 @@ public class APIMessage implements Serializable {
|
|||
}
|
||||
|
||||
public APIMessageException(final APIMessage apiMessage) {
|
||||
super();
|
||||
super(apiMessage.systemMessage + " " + apiMessage.details);
|
||||
this.apiMessages = Arrays.asList(apiMessage);
|
||||
}
|
||||
|
||||
|
|
|
@ -18,4 +18,15 @@ public interface APIMessageError {
|
|||
* @return a List of APIMessage errors if error happened or empty list of not */
|
||||
Collection<APIMessage> getAPIMessages();
|
||||
|
||||
/** Get the main APIMessage (first APIMessage in the list) or null if none available
|
||||
*
|
||||
* @return the main APIMessage or null if none available */
|
||||
default APIMessage getMainMessage() {
|
||||
final Collection<APIMessage> apiMessages = getAPIMessages();
|
||||
if (apiMessages != null && !apiMessages.isEmpty()) {
|
||||
return apiMessages.iterator().next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ package ch.ethz.seb.sebserver.gbl.api;
|
|||
|
||||
import javax.annotation.Generated;
|
||||
|
||||
@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2022-01-18T17:36:21.033+01:00")
|
||||
@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2022-05-16T11:24:18.256+02:00")
|
||||
public enum EntityType {
|
||||
CONFIGURATION_ATTRIBUTE,
|
||||
CONFIGURATION_VALUE,
|
||||
|
|
|
@ -14,7 +14,10 @@ import java.util.Base64;
|
|||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
|
@ -226,6 +229,14 @@ public class POSTMapper {
|
|||
return Utils.toDateTime(value);
|
||||
}
|
||||
|
||||
public Map<String, String> getSubMap(final Set<String> actionAttributes) {
|
||||
return this.params
|
||||
.keySet()
|
||||
.stream()
|
||||
.filter(actionAttributes::contains)
|
||||
.collect(Collectors.toMap(Function.identity(), k -> this.params.getFirst(k)));
|
||||
}
|
||||
|
||||
public List<Threshold> getThresholds() {
|
||||
final List<String> thresholdStrings = this.params.get(Domain.THRESHOLD.REFERENCE_NAME);
|
||||
if (thresholdStrings == null || thresholdStrings.isEmpty()) {
|
||||
|
@ -237,14 +248,20 @@ public class POSTMapper {
|
|||
.map(ts -> {
|
||||
try {
|
||||
final String[] split = StringUtils.split(ts, Constants.EMBEDDED_LIST_SEPARATOR);
|
||||
return new Threshold(Double.parseDouble(
|
||||
split[0]),
|
||||
(split.length > 1) ? split[1] : null,
|
||||
(split.length > 2) ? split[2] : null);
|
||||
Double val = null;
|
||||
try {
|
||||
val = Double.parseDouble(split[0]);
|
||||
} catch (final Exception e) {
|
||||
}
|
||||
return new Threshold(
|
||||
val,
|
||||
(split.length > 1) ? Utils.parseColorString(Utils.parseRGB(split[1])) : null,
|
||||
(split.length > 2 && "null".equals(split[2])) ? split[2] : null);
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
|
|
@ -29,7 +29,7 @@ public enum PrivilegeType {
|
|||
* and so on.
|
||||
*
|
||||
* @param type the PrivilegeType
|
||||
* @return true if given PrivilegeType is implicit form this PrivilegeType */
|
||||
* @return true if given PrivilegeType is implicit from this PrivilegeType */
|
||||
public boolean hasImplicit(final PrivilegeType type) {
|
||||
if (type == null) {
|
||||
return false;
|
||||
|
|
|
@ -36,7 +36,7 @@ public class AsyncService {
|
|||
*
|
||||
* @param maxFailingAttempts maximal number of attempts the CircuitBreaker allows before going onto open state.
|
||||
* @param maxBlockingTime maximal time since call CircuitBreaker waits for a response before going onto open state.
|
||||
* @param timeToRecover the time the CircuitBreaker takes to recover form open state.
|
||||
* @param timeToRecover the time the CircuitBreaker takes to recover from open state.
|
||||
* @param <T> the type of the CircuitBreaker
|
||||
* @return a CircuitBreaker of specified type */
|
||||
public <T> CircuitBreaker<T> createCircuitBreaker(
|
||||
|
@ -57,7 +57,7 @@ public class AsyncService {
|
|||
* @param blockingSupplier the blocking result supplier that the MemoizingCircuitBreaker must call
|
||||
* @param maxFailingAttempts maximal number of attempts the CircuitBreaker allows before going onto open state.
|
||||
* @param maxBlockingTime maximal time since call CircuitBreaker waits for a response before going onto open state.
|
||||
* @param timeToRecover the time the CircuitBreaker takes to recover form open state.
|
||||
* @param timeToRecover the time the CircuitBreaker takes to recover from open state.
|
||||
* @param momoized whether the memoizing functionality is on or off
|
||||
* @param maxMemoizingTime the maximal time memorized data is valid
|
||||
* @param <T> the type of the CircuitBreaker
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gbl.async;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
@ -50,13 +52,11 @@ public final class CircuitBreaker<T> {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(CircuitBreaker.class);
|
||||
|
||||
public static final String OPEN_CIRCUIT_BREAKER_EXCEPTION = "Open CircuitBreaker";
|
||||
public static final int DEFAULT_MAX_FAILING_ATTEMPTS = 5;
|
||||
public static final long DEFAULT_MAX_BLOCKING_TIME = Constants.MINUTE_IN_MILLIS;
|
||||
public static final long DEFAULT_TIME_TO_RECOVER = Constants.MINUTE_IN_MILLIS * 10;
|
||||
|
||||
public static final RuntimeException OPEN_STATE_EXCEPTION =
|
||||
new RuntimeException("Open CircuitBreaker");
|
||||
|
||||
public enum State {
|
||||
CLOSED,
|
||||
HALF_OPEN,
|
||||
|
@ -87,7 +87,7 @@ public final class CircuitBreaker<T> {
|
|||
/** Create new CircuitBreakerSupplier.
|
||||
*
|
||||
* @param asyncRunner the AsyncRunner used to create asynchronous calls on the given supplier function
|
||||
* @param maxFailingAttempts the number of maximal failing attempts before go form CLOSE into HALF_OPEN state
|
||||
* @param maxFailingAttempts the number of maximal failing attempts before go from CLOSE into HALF_OPEN state
|
||||
* @param maxBlockingTime the maximal time that an call attempt can block until an error is responded
|
||||
* @param timeToRecover the time the circuit breaker needs to cool-down on OPEN-STATE before going back to HALF_OPEN
|
||||
* state */
|
||||
|
@ -169,10 +169,10 @@ public final class CircuitBreaker<T> {
|
|||
|
||||
final long currentBlockingTime = Utils.getMillisecondsNow() - startTime;
|
||||
final int failing = this.failingCount.incrementAndGet();
|
||||
if (failing > this.maxFailingAttempts || currentBlockingTime > this.maxBlockingTime) {
|
||||
if (failing >= this.maxFailingAttempts || currentBlockingTime > this.maxBlockingTime) {
|
||||
// brake thought to HALF_OPEN state and return error
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Changing state from Open to Half Open and return cached value");
|
||||
log.debug("Changing state from Open to Half Open");
|
||||
}
|
||||
|
||||
this.state = State.HALF_OPEN;
|
||||
|
@ -203,7 +203,7 @@ public final class CircuitBreaker<T> {
|
|||
if (result.hasError()) {
|
||||
// on fail go to OPEN state
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Changing state from Half Open to Open and return cached value");
|
||||
log.debug("Changing state from Half Open to Open");
|
||||
}
|
||||
|
||||
this.lastOpenTime = Utils.getMillisecondsNow();
|
||||
|
@ -214,7 +214,7 @@ public final class CircuitBreaker<T> {
|
|||
} else {
|
||||
// on success go to CLOSED state
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Changing state from Half Open to Closed and return value");
|
||||
log.debug("Changing state from Half Open to Closed");
|
||||
}
|
||||
|
||||
this.state = State.CLOSED;
|
||||
|
@ -243,14 +243,26 @@ public final class CircuitBreaker<T> {
|
|||
return protectedRun(supplier);
|
||||
}
|
||||
|
||||
return Result.ofError(OPEN_STATE_EXCEPTION);
|
||||
return Result.ofError(new RuntimeException(OPEN_CIRCUIT_BREAKER_EXCEPTION));
|
||||
}
|
||||
|
||||
private Result<T> attempt(final Supplier<T> supplier) {
|
||||
final Future<T> future = this.asyncRunner.runAsync(supplier);
|
||||
|
||||
try {
|
||||
return Result.of(future.get(this.maxBlockingTime, TimeUnit.MILLISECONDS));
|
||||
} catch (final Exception e) {
|
||||
} catch (final InterruptedException e) {
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Attempt interruption: {}, {}", e.getMessage(), this.state);
|
||||
}
|
||||
return Result.ofError(e);
|
||||
} catch (final ExecutionException e) {
|
||||
future.cancel(false);
|
||||
if (log.isWarnEnabled()) {
|
||||
log.warn("Attempt error: {}, {}", e.getMessage(), this.state);
|
||||
}
|
||||
return Result.ofError(e);
|
||||
} catch (final TimeoutException e) {
|
||||
future.cancel(false);
|
||||
log.warn("Max blocking timeout exceeded: {}, {}", this.maxBlockingTime, this.state);
|
||||
return Result.ofError(e);
|
||||
|
|
|
@ -94,7 +94,7 @@ public final class MemoizingCircuitBreaker<T> implements Supplier<Result<T>> {
|
|||
*
|
||||
* @param asyncRunner the AsyncRunner used to create asynchronous calls on the given supplier function
|
||||
* @param supplier The Supplier function that can fail or block for a long time
|
||||
* @param maxFailingAttempts the number of maximal failing attempts before go form CLOSE into HALF_OPEN state
|
||||
* @param maxFailingAttempts the number of maximal failing attempts before go from CLOSE into HALF_OPEN state
|
||||
* @param maxBlockingTime the maximal time that an call attempt can block until an error is responded
|
||||
* @param timeToRecover the time the circuit breaker needs to cool-down on OPEN-STATE before going back to HALF_OPEN
|
||||
* state
|
||||
|
|
|
@ -55,13 +55,13 @@ public interface ClientCredentialService {
|
|||
return encryptClientCredentials(clientIdPlaintext, secretPlaintext, null);
|
||||
}
|
||||
|
||||
/** Use this to get a decrypted plain text secret form given ClientCredentials
|
||||
/** Use this to get a decrypted plain text secret from given ClientCredentials
|
||||
*
|
||||
* @param credentials ClientCredentials containing the secret to decrypt
|
||||
* @return decrypted plain text secret */
|
||||
Result<CharSequence> getPlainClientSecret(ClientCredentials credentials);
|
||||
|
||||
/** Use this to get a decrypted plain text accessToken form given ClientCredentials
|
||||
/** Use this to get a decrypted plain text accessToken from given ClientCredentials
|
||||
*
|
||||
* @param credentials ClientCredentials containing the accessToken to decrypt
|
||||
* @return decrypted plain text accessToken */
|
||||
|
|
|
@ -8,20 +8,34 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gbl.client;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
/** Defines a simple data bean holding (encrypted) client credentials */
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class ClientCredentials {
|
||||
|
||||
public static final String ATTR_CLIENT_ID = "clientId";
|
||||
public static final String ATTR_SECRET = "secret";
|
||||
public static final String ATTR_ACCESS_TOKEN = "accessToken";
|
||||
|
||||
/** The client id or client name parameter */
|
||||
@JsonProperty(ATTR_CLIENT_ID)
|
||||
public final CharSequence clientId;
|
||||
/** The client secret parameter */
|
||||
@JsonProperty(ATTR_SECRET)
|
||||
public final CharSequence secret;
|
||||
/** An client access token if supported */
|
||||
@JsonProperty(ATTR_ACCESS_TOKEN)
|
||||
public final CharSequence accessToken;
|
||||
|
||||
@JsonCreator
|
||||
public ClientCredentials(
|
||||
final CharSequence clientId,
|
||||
final CharSequence secret,
|
||||
final CharSequence accessToken) {
|
||||
@JsonProperty(ATTR_CLIENT_ID) final CharSequence clientId,
|
||||
@JsonProperty(ATTR_SECRET) final CharSequence secret,
|
||||
@JsonProperty(ATTR_ACCESS_TOKEN) final CharSequence accessToken) {
|
||||
|
||||
this.clientId = clientId;
|
||||
this.secret = secret;
|
||||
|
@ -35,26 +49,44 @@ public final class ClientCredentials {
|
|||
this(clientId, secret, null);
|
||||
}
|
||||
|
||||
public CharSequence getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
public CharSequence getSecret() {
|
||||
return this.secret;
|
||||
}
|
||||
|
||||
public CharSequence getAccessToken() {
|
||||
return this.accessToken;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean hasClientId() {
|
||||
return this.clientId != null && this.clientId.length() > 0;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean hasSecret() {
|
||||
return this.secret != null && this.secret.length() > 0;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean hasAccessToken() {
|
||||
return this.accessToken != null && this.accessToken.length() > 0;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String clientIdAsString() {
|
||||
return hasClientId() ? this.clientId.toString() : null;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String secretAsString() {
|
||||
return hasSecret() ? this.secret.toString() : null;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String accessTokenAsString() {
|
||||
return hasAccessToken() ? this.accessToken.toString() : null;
|
||||
}
|
||||
|
|
|
@ -8,18 +8,36 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gbl.model;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain.BATCH_ACTION;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
public class BatchAction implements Entity {
|
||||
public class BatchAction implements GrantEntity {
|
||||
|
||||
public static final String ATTR_FAILURES = "failures";
|
||||
public static final String FINISHED_FLAG = "_FINISHED";
|
||||
public static final String ACTION_ATTRIBUT_TARGET_STATE = "batchActionTargetState";
|
||||
|
||||
private static final Set<String> ACTION_ATTRIBUTES = new HashSet<>(Arrays.asList(
|
||||
ACTION_ATTRIBUT_TARGET_STATE));
|
||||
|
||||
@JsonProperty(BATCH_ACTION.ATTR_ID)
|
||||
public final Long id;
|
||||
|
@ -28,10 +46,17 @@ public class BatchAction implements Entity {
|
|||
@JsonProperty(BATCH_ACTION.ATTR_INSTITUTION_ID)
|
||||
public final Long institutionId;
|
||||
|
||||
@NotNull
|
||||
@JsonProperty(BATCH_ACTION.ATTR_OWNER)
|
||||
public final String ownerId;
|
||||
|
||||
@NotNull
|
||||
@JsonProperty(BATCH_ACTION.ATTR_ACTION_TYPE)
|
||||
public final BatchActionType actionType;
|
||||
|
||||
@JsonProperty(BATCH_ACTION.ATTR_ATTRIBUTES)
|
||||
public final Map<String, String> attributes;
|
||||
|
||||
@NotNull
|
||||
@JsonProperty(BATCH_ACTION.ATTR_SOURCE_IDS)
|
||||
public final Collection<String> sourceIds;
|
||||
|
@ -45,23 +70,49 @@ public class BatchAction implements Entity {
|
|||
@JsonProperty(BATCH_ACTION.ATTR_PROCESSOR_ID)
|
||||
public final String processorId;
|
||||
|
||||
@JsonProperty(ATTR_FAILURES)
|
||||
public final Map<String, APIMessage> failures;
|
||||
|
||||
public BatchAction(
|
||||
@JsonProperty(BATCH_ACTION.ATTR_ID) final Long id,
|
||||
@JsonProperty(BATCH_ACTION.ATTR_INSTITUTION_ID) final Long institutionId,
|
||||
@JsonProperty(BATCH_ACTION.ATTR_OWNER) final String ownerId,
|
||||
@JsonProperty(BATCH_ACTION.ATTR_ACTION_TYPE) final BatchActionType actionType,
|
||||
@JsonProperty(BATCH_ACTION.ATTR_ATTRIBUTES) final Map<String, String> attributes,
|
||||
@JsonProperty(BATCH_ACTION.ATTR_SOURCE_IDS) final Collection<String> sourceIds,
|
||||
@JsonProperty(BATCH_ACTION.ATTR_SUCCESSFUL) final Collection<String> successful,
|
||||
@JsonProperty(BATCH_ACTION.ATTR_LAST_UPDATE) final Long lastUpdate,
|
||||
@JsonProperty(BATCH_ACTION.ATTR_PROCESSOR_ID) final String processorId) {
|
||||
@JsonProperty(BATCH_ACTION.ATTR_PROCESSOR_ID) final String processorId,
|
||||
@JsonProperty(ATTR_FAILURES) final Map<String, APIMessage> failures) {
|
||||
|
||||
super();
|
||||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
this.ownerId = ownerId;
|
||||
this.actionType = actionType;
|
||||
this.attributes = Utils.immutableMapOf(attributes);
|
||||
this.sourceIds = Utils.immutableCollectionOf(sourceIds);
|
||||
this.successful = Utils.immutableCollectionOf(successful);
|
||||
this.lastUpdate = lastUpdate;
|
||||
this.processorId = processorId;
|
||||
this.failures = Utils.immutableMapOf(failures);
|
||||
}
|
||||
|
||||
public BatchAction(final String modelId, final String ownerId, final POSTMapper postMap) {
|
||||
|
||||
super();
|
||||
this.id = (modelId != null) ? Long.parseLong(modelId) : null;
|
||||
this.institutionId = postMap.getLong(BATCH_ACTION.ATTR_INSTITUTION_ID);
|
||||
this.ownerId = ownerId;
|
||||
this.actionType = postMap.getEnum(BATCH_ACTION.ATTR_ACTION_TYPE, BatchActionType.class);
|
||||
this.attributes = postMap.getSubMap(ACTION_ATTRIBUTES);
|
||||
this.sourceIds = Utils.immutableListOf(StringUtils.split(
|
||||
postMap.getString(BATCH_ACTION.ATTR_SOURCE_IDS),
|
||||
Constants.LIST_SEPARATOR));
|
||||
this.successful = Collections.emptyList();
|
||||
this.lastUpdate = null;
|
||||
this.processorId = null;
|
||||
this.failures = Collections.emptyMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -85,14 +136,24 @@ public class BatchAction implements Entity {
|
|||
return this.id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getInstitutionId() {
|
||||
return this.institutionId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getOwnerId() {
|
||||
return this.ownerId;
|
||||
}
|
||||
|
||||
public BatchActionType getActionType() {
|
||||
return this.actionType;
|
||||
}
|
||||
|
||||
public Map<String, String> getAttributes() {
|
||||
return this.attributes;
|
||||
}
|
||||
|
||||
public Collection<String> getSourceIds() {
|
||||
return this.sourceIds;
|
||||
}
|
||||
|
@ -109,6 +170,20 @@ public class BatchAction implements Entity {
|
|||
return this.processorId;
|
||||
}
|
||||
|
||||
public Map<String, APIMessage> getFailures() {
|
||||
return this.failures;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public int getProgress() {
|
||||
return 100 / this.sourceIds.size() * (this.successful.size() + this.failures.size());
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isFinished() {
|
||||
return this.processorId != null && this.processorId.contains(FINISHED_FLAG);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
final int prime = 31;
|
||||
|
@ -143,6 +218,8 @@ public class BatchAction implements Entity {
|
|||
builder.append(this.institutionId);
|
||||
builder.append(", actionType=");
|
||||
builder.append(this.actionType);
|
||||
builder.append(", attributes=");
|
||||
builder.append(this.attributes);
|
||||
builder.append(", sourceIds=");
|
||||
builder.append(this.sourceIds);
|
||||
builder.append(", successful=");
|
||||
|
|
|
@ -5,7 +5,7 @@ import javax.annotation.Generated;
|
|||
/** Defines the global names of the domain model and domain model fields.
|
||||
* This shall be used as a static overall domain model names reference within SEB Server Web-Service as well as within the integrated GUI
|
||||
* This file is generated by the org.eth.demo.sebserver.gen.DomainModelNameReferencePlugin and must not be edited manually.**/
|
||||
@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2022-01-18T17:36:20.975+01:00")
|
||||
@Generated(value="org.mybatis.generator.api.MyBatisGenerator",comments="ch.ethz.seb.sebserver.gen.DomainModelNameReferencePlugin",date="2022-05-16T11:24:18.186+02:00")
|
||||
public interface Domain {
|
||||
|
||||
interface CONFIGURATION_ATTRIBUTE {
|
||||
|
@ -79,6 +79,8 @@ public interface Domain {
|
|||
String ATTR_DESCRIPTION = "description";
|
||||
String ATTR_TYPE = "type";
|
||||
String ATTR_STATUS = "status";
|
||||
String ATTR_LAST_UPDATE_TIME = "lastUpdateTime";
|
||||
String ATTR_LAST_UPDATE_USER = "lastUpdateUser";
|
||||
}
|
||||
|
||||
interface EXAM_CONFIGURATION_MAP {
|
||||
|
@ -111,6 +113,10 @@ public interface Domain {
|
|||
String ATTR_ACTIVE = "active";
|
||||
String ATTR_EXAM_TEMPLATE_ID = "examTemplateId";
|
||||
String ATTR_LAST_MODIFIED = "lastModified";
|
||||
String ATTR_QUIZ_NAME = "quizName";
|
||||
String ATTR_QUIZ_START_TIME = "quizStartTime";
|
||||
String ATTR_QUIZ_END_TIME = "quizEndTime";
|
||||
String ATTR_LMS_AVAILABLE = "lmsAvailable";
|
||||
}
|
||||
|
||||
interface CLIENT_CONNECTION {
|
||||
|
@ -217,6 +223,8 @@ public interface Domain {
|
|||
String ATTR_CLIENT_SECRET = "clientSecret";
|
||||
String ATTR_ENCRYPT_SECRET = "encryptSecret";
|
||||
String ATTR_ACTIVE = "active";
|
||||
String ATTR_LAST_UPDATE_TIME = "lastUpdateTime";
|
||||
String ATTR_LAST_UPDATE_USER = "lastUpdateUser";
|
||||
}
|
||||
|
||||
interface LMS_SETUP {
|
||||
|
@ -323,7 +331,9 @@ public interface Domain {
|
|||
String REFERENCE_NAME = "batchActions";
|
||||
String ATTR_ID = "id";
|
||||
String ATTR_INSTITUTION_ID = "institutionId";
|
||||
String ATTR_OWNER = "owner";
|
||||
String ATTR_ACTION_TYPE = "actionType";
|
||||
String ATTR_ATTRIBUTES = "attributes";
|
||||
String ATTR_SOURCE_IDS = "sourceIds";
|
||||
String ATTR_SUCCESSFUL = "successful";
|
||||
String ATTR_LAST_UPDATE = "lastUpdate";
|
||||
|
|
|
@ -48,7 +48,7 @@ public interface Entity extends ModelIdAware {
|
|||
|
||||
/** Creates an EntityName instance from this Entity instance.
|
||||
*
|
||||
* @return EntityName instance created form given Entity */
|
||||
* @return EntityName instance created from given Entity */
|
||||
default EntityName toName() {
|
||||
return new EntityName(
|
||||
this.getModelId(),
|
||||
|
|
|
@ -11,8 +11,10 @@ package ch.ethz.seb.sebserver.gbl.model;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class EntityDependency implements Comparable<EntityDependency> {
|
||||
public class EntityDependency implements Comparable<EntityDependency>, ModelIdAware {
|
||||
|
||||
public static final String ATTR_PARENT = "parent";
|
||||
public static final String ATTR_SELF = "self";
|
||||
|
@ -48,6 +50,11 @@ public class EntityDependency implements Comparable<EntityDependency> {
|
|||
return this.self;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelId() {
|
||||
return this.self.modelId;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.name;
|
||||
}
|
||||
|
@ -108,9 +115,16 @@ public class EntityDependency implements Comparable<EntityDependency> {
|
|||
return -1;
|
||||
}
|
||||
|
||||
final int compareTo = this.self.entityType.name().compareTo(other.self.entityType.name());
|
||||
final int compareTo = Integer.compare(
|
||||
Constants.ENTITY_TYPE_HIRARCHIE.indexOf(this.self.entityType),
|
||||
Constants.ENTITY_TYPE_HIRARCHIE.indexOf(other.self.entityType));
|
||||
//this.self.entityType.name().compareTo(other.self.entityType.name());
|
||||
if (compareTo == 0) {
|
||||
return this.self.modelId.compareTo(other.self.modelId);
|
||||
if (this.name != null) {
|
||||
return this.name.compareTo(other.name);
|
||||
} else {
|
||||
return this.self.modelId.compareTo(other.self.modelId);
|
||||
}
|
||||
} else {
|
||||
return compareTo;
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import com.fasterxml.jackson.annotation.JsonCreator;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
|
||||
/** An EntityKey uniquely identifies a domain entity within the SEB Server's domain model.
|
||||
|
@ -128,7 +129,11 @@ public class EntityKey implements ModelIdAware, Serializable, Comparable<EntityK
|
|||
return -1;
|
||||
}
|
||||
|
||||
final int compareTo = this.entityType.name().compareTo(other.entityType.name());
|
||||
final int compareTo = Constants.DEFAULT_ENGLISH_COLLATOR.compare(
|
||||
this.entityType.name(),
|
||||
other.entityType.name());
|
||||
//this.entityType.name().compareTo(other.entityType.name());
|
||||
|
||||
if (compareTo == 0) {
|
||||
return this.modelId.compareTo(other.modelId);
|
||||
} else {
|
||||
|
|
|
@ -11,10 +11,12 @@ package ch.ethz.seb.sebserver.gbl.model.exam;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
|
@ -29,6 +31,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
|||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class Exam implements GrantEntity {
|
||||
|
@ -38,11 +41,10 @@ public final class Exam implements GrantEntity {
|
|||
-1L,
|
||||
-1L,
|
||||
Constants.EMPTY_NOTE,
|
||||
Constants.EMPTY_NOTE,
|
||||
false,
|
||||
Constants.EMPTY_NOTE,
|
||||
null,
|
||||
null,
|
||||
Constants.EMPTY_NOTE,
|
||||
ExamType.UNDEFINED,
|
||||
null,
|
||||
null,
|
||||
|
@ -65,8 +67,7 @@ public final class Exam implements GrantEntity {
|
|||
UP_COMING,
|
||||
RUNNING,
|
||||
FINISHED,
|
||||
CORRUPT_NO_LMS_CONNECTION,
|
||||
CORRUPT_INVALID_ID
|
||||
ARCHIVED
|
||||
}
|
||||
|
||||
public enum ExamType {
|
||||
|
@ -91,21 +92,18 @@ public final class Exam implements GrantEntity {
|
|||
@NotNull
|
||||
public final String externalId;
|
||||
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_NAME)
|
||||
@JsonProperty(EXAM.ATTR_LMS_AVAILABLE)
|
||||
public final Boolean lmsAvailable;
|
||||
|
||||
@JsonProperty(EXAM.ATTR_QUIZ_NAME)
|
||||
public final String name;
|
||||
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_DESCRIPTION)
|
||||
public final String description;
|
||||
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_START_TIME)
|
||||
@JsonProperty(EXAM.ATTR_QUIZ_START_TIME)
|
||||
public final DateTime startTime;
|
||||
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_END_TIME)
|
||||
@JsonProperty(EXAM.ATTR_QUIZ_END_TIME)
|
||||
public final DateTime endTime;
|
||||
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_START_URL)
|
||||
public final String startURL;
|
||||
|
||||
@JsonProperty(EXAM.ATTR_TYPE)
|
||||
@NotNull
|
||||
public final ExamType type;
|
||||
|
@ -138,7 +136,7 @@ public final class Exam implements GrantEntity {
|
|||
public final Long lastModified;
|
||||
|
||||
@JsonProperty(ATTR_ADDITIONAL_ATTRIBUTES)
|
||||
private final Map<String, String> additionalAttributes;
|
||||
public final Map<String, String> additionalAttributes;
|
||||
|
||||
@JsonCreator
|
||||
public Exam(
|
||||
|
@ -146,11 +144,10 @@ public final class Exam implements GrantEntity {
|
|||
@JsonProperty(EXAM.ATTR_INSTITUTION_ID) final Long institutionId,
|
||||
@JsonProperty(EXAM.ATTR_LMS_SETUP_ID) final Long lmsSetupId,
|
||||
@JsonProperty(EXAM.ATTR_EXTERNAL_ID) final String externalId,
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_NAME) final String name,
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_DESCRIPTION) final String description,
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_START_TIME) final DateTime startTime,
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_END_TIME) final DateTime endTime,
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_START_URL) final String startURL,
|
||||
@JsonProperty(EXAM.ATTR_LMS_AVAILABLE) final Boolean lmsAvailable,
|
||||
@JsonProperty(EXAM.ATTR_QUIZ_NAME) final String name,
|
||||
@JsonProperty(EXAM.ATTR_QUIZ_START_TIME) final DateTime startTime,
|
||||
@JsonProperty(EXAM.ATTR_QUIZ_END_TIME) final DateTime endTime,
|
||||
@JsonProperty(EXAM.ATTR_TYPE) final ExamType type,
|
||||
@JsonProperty(EXAM.ATTR_OWNER) final String owner,
|
||||
@JsonProperty(EXAM.ATTR_SUPPORTER) final Collection<String> supporter,
|
||||
|
@ -167,11 +164,10 @@ public final class Exam implements GrantEntity {
|
|||
this.institutionId = institutionId;
|
||||
this.lmsSetupId = lmsSetupId;
|
||||
this.externalId = externalId;
|
||||
this.lmsAvailable = lmsAvailable;
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.startTime = startTime;
|
||||
this.endTime = endTime;
|
||||
this.startURL = startURL;
|
||||
this.type = type;
|
||||
this.owner = owner;
|
||||
this.status = (status != null) ? status : getStatusFromDate(startTime, endTime);
|
||||
|
@ -191,15 +187,18 @@ public final class Exam implements GrantEntity {
|
|||
|
||||
public Exam(final String modelId, final QuizData quizData, final POSTMapper mapper) {
|
||||
|
||||
final Map<String, String> additionalAttributes = new HashMap<>(quizData.getAdditionalAttributes());
|
||||
additionalAttributes.put(QuizData.QUIZ_ATTR_DESCRIPTION, quizData.description);
|
||||
additionalAttributes.put(QuizData.QUIZ_ATTR_START_URL, quizData.startURL);
|
||||
|
||||
this.id = (modelId != null) ? Long.parseLong(modelId) : null;
|
||||
this.institutionId = quizData.institutionId;
|
||||
this.lmsSetupId = quizData.lmsSetupId;
|
||||
this.externalId = quizData.id;
|
||||
this.lmsAvailable = true;
|
||||
this.name = quizData.name;
|
||||
this.description = quizData.description;
|
||||
this.startTime = quizData.startTime;
|
||||
this.endTime = quizData.endTime;
|
||||
this.startURL = quizData.startURL;
|
||||
this.type = mapper.getEnum(EXAM.ATTR_TYPE, ExamType.class, ExamType.UNDEFINED);
|
||||
this.owner = mapper.getString(EXAM.ATTR_OWNER);
|
||||
this.status = mapper.getEnum(
|
||||
|
@ -213,7 +212,8 @@ public final class Exam implements GrantEntity {
|
|||
this.lastUpdate = null;
|
||||
this.examTemplateId = mapper.getLong(EXAM.ATTR_EXAM_TEMPLATE_ID);
|
||||
this.lastModified = null;
|
||||
this.additionalAttributes = null;
|
||||
this.additionalAttributes = Utils.immutableMapOf(additionalAttributes);
|
||||
|
||||
}
|
||||
|
||||
public Exam(final QuizData quizData) {
|
||||
|
@ -225,11 +225,10 @@ public final class Exam implements GrantEntity {
|
|||
this.institutionId = null;
|
||||
this.lmsSetupId = null;
|
||||
this.externalId = null;
|
||||
this.lmsAvailable = true;
|
||||
this.name = null;
|
||||
this.description = null;
|
||||
this.startTime = null;
|
||||
this.endTime = null;
|
||||
this.startURL = null;
|
||||
this.type = null;
|
||||
this.owner = null;
|
||||
this.status = (status != null) ? status : getStatusFromDate(this.startTime, this.endTime);
|
||||
|
@ -293,6 +292,15 @@ public final class Exam implements GrantEntity {
|
|||
return this.lmsSetupId;
|
||||
}
|
||||
|
||||
public Boolean getLmsAvailable() {
|
||||
return this.lmsAvailable;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public boolean isLmsAvailable() {
|
||||
return BooleanUtils.isTrue(this.lmsAvailable);
|
||||
}
|
||||
|
||||
public String getExternalId() {
|
||||
return this.externalId;
|
||||
}
|
||||
|
@ -311,7 +319,7 @@ public final class Exam implements GrantEntity {
|
|||
}
|
||||
|
||||
public String getDescription() {
|
||||
return this.description;
|
||||
return this.getAdditionalAttribute(QuizData.QUIZ_ATTR_DESCRIPTION);
|
||||
}
|
||||
|
||||
public DateTime getStartTime() {
|
||||
|
@ -323,7 +331,7 @@ public final class Exam implements GrantEntity {
|
|||
}
|
||||
|
||||
public String getStartURL() {
|
||||
return this.startURL;
|
||||
return this.getAdditionalAttribute(QuizData.QUIZ_ATTR_START_URL);
|
||||
}
|
||||
|
||||
public ExamStatus getStatus() {
|
||||
|
@ -372,13 +380,13 @@ public final class Exam implements GrantEntity {
|
|||
builder.append(", name=");
|
||||
builder.append(this.name);
|
||||
builder.append(", description=");
|
||||
builder.append(this.description);
|
||||
builder.append(this.getDescription());
|
||||
builder.append(", startTime=");
|
||||
builder.append(this.startTime);
|
||||
builder.append(", endTime=");
|
||||
builder.append(this.endTime);
|
||||
builder.append(", startURL=");
|
||||
builder.append(this.startURL);
|
||||
builder.append(this.getStartURL());
|
||||
builder.append(", type=");
|
||||
builder.append(this.type);
|
||||
builder.append(", owner=");
|
||||
|
|
|
@ -26,12 +26,15 @@ import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM;
|
|||
import ch.ethz.seb.sebserver.gbl.model.Domain.EXAM_CONFIGURATION_MAP;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public final class ExamConfigurationMap implements GrantEntity {
|
||||
|
||||
private static final String ARR_EXAM_STATUS = "examStatus";
|
||||
|
||||
public static final String ATTR_CONFIRM_ENCRYPT_SECRET = "confirm_encrypt_secret";
|
||||
|
||||
public static final String FILTER_ATTR_EXAM_ID = "examId";
|
||||
|
@ -60,6 +63,9 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
@JsonProperty(EXAM.ATTR_TYPE)
|
||||
public final ExamType examType;
|
||||
|
||||
@JsonProperty(ARR_EXAM_STATUS)
|
||||
public final ExamStatus examStatus;
|
||||
|
||||
@NotNull(message = "examConfigurationMap:configurationNodeId:notNull")
|
||||
@JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID)
|
||||
public final Long configurationNodeId;
|
||||
|
@ -91,6 +97,7 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
@JsonProperty(QuizData.QUIZ_ATTR_DESCRIPTION) final String examDescription,
|
||||
@JsonProperty(QuizData.QUIZ_ATTR_START_TIME) final DateTime examStartTime,
|
||||
@JsonProperty(EXAM.ATTR_TYPE) final ExamType examType,
|
||||
@JsonProperty(ARR_EXAM_STATUS) final ExamStatus examStatus,
|
||||
@JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID) final Long configurationNodeId,
|
||||
@JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_USER_NAMES) final String userNames,
|
||||
@JsonProperty(EXAM_CONFIGURATION_MAP.ATTR_ENCRYPT_SECRET) final CharSequence encryptSecret,
|
||||
|
@ -106,6 +113,7 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
this.examDescription = examDescription;
|
||||
this.examStartTime = examStartTime;
|
||||
this.examType = examType;
|
||||
this.examStatus = examStatus;
|
||||
this.configurationNodeId = configurationNodeId;
|
||||
this.userNames = userNames;
|
||||
this.encryptSecret = encryptSecret;
|
||||
|
@ -125,6 +133,7 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
this.examDescription = postParams.getString(QuizData.QUIZ_ATTR_DESCRIPTION);
|
||||
this.examStartTime = postParams.getDateTime(QuizData.QUIZ_ATTR_START_TIME);
|
||||
this.examType = postParams.getEnum(EXAM.ATTR_TYPE, ExamType.class);
|
||||
this.examStatus = postParams.getEnum(ARR_EXAM_STATUS, ExamStatus.class);
|
||||
|
||||
this.configurationNodeId = postParams.getLong(Domain.EXAM_CONFIGURATION_MAP.ATTR_CONFIGURATION_NODE_ID);
|
||||
this.userNames = postParams.getString(Domain.EXAM_CONFIGURATION_MAP.ATTR_USER_NAMES);
|
||||
|
@ -149,6 +158,7 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
this.examDescription = null;
|
||||
this.examStartTime = null;
|
||||
this.examType = null;
|
||||
this.examStatus = null;
|
||||
this.configurationNodeId = configurationNodeId;
|
||||
this.userNames = userNames;
|
||||
this.encryptSecret = null;
|
||||
|
@ -205,6 +215,10 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
return this.examType;
|
||||
}
|
||||
|
||||
public ExamStatus getExamStatus() {
|
||||
return this.examStatus;
|
||||
}
|
||||
|
||||
public Long getConfigurationNodeId() {
|
||||
return this.configurationNodeId;
|
||||
}
|
||||
|
@ -250,6 +264,7 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
this.examDescription,
|
||||
this.examStartTime,
|
||||
this.examType,
|
||||
this.examStatus,
|
||||
this.configurationNodeId,
|
||||
this.userNames,
|
||||
Constants.EMPTY_NOTE,
|
||||
|
@ -296,7 +311,8 @@ public final class ExamConfigurationMap implements GrantEntity {
|
|||
|
||||
public static ExamConfigurationMap createNew(final Exam exam) {
|
||||
return new ExamConfigurationMap(
|
||||
null, exam.institutionId, exam.id, exam.name, exam.description, exam.startTime, exam.type,
|
||||
null, exam.institutionId, exam.id, exam.name, exam.getDescription(), exam.startTime, exam.type,
|
||||
exam.status,
|
||||
null, null, null, null, null, null, null);
|
||||
}
|
||||
|
||||
|
|
|
@ -240,6 +240,7 @@ public final class Indicator implements Entity {
|
|||
}
|
||||
}
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public static final class Threshold implements Comparable<Threshold> {
|
||||
|
||||
@JsonProperty(THRESHOLD.ATTR_VALUE)
|
||||
|
|
|
@ -56,7 +56,7 @@ public final class LmsSetup implements GrantEntity, Activatable {
|
|||
public enum LmsType {
|
||||
/** Mockup LMS type used to create test setups */
|
||||
MOCKUP(Features.COURSE_API),
|
||||
/** The Open edX LMS binding features both APIs, course access as well as SEB restrcition */
|
||||
/** The Open edX LMS binding features both APIs, course access as well as SEB restriction */
|
||||
OPEN_EDX(Features.COURSE_API, Features.SEB_RESTRICTION),
|
||||
/** The Moodle binding features only the course access API so far */
|
||||
MOODLE(Features.COURSE_API /* , Features.SEB_RESTRICTION */),
|
||||
|
|
|
@ -28,6 +28,7 @@ public final class LmsSetupTestResult {
|
|||
public static final String ATTR_MISSING_ATTRIBUTE = "missingLMSSetupAttribute";
|
||||
|
||||
public enum ErrorType {
|
||||
API_NOT_SUPPORTED,
|
||||
MISSING_ATTRIBUTE,
|
||||
TOKEN_REQUEST,
|
||||
QUIZ_ACCESS_API_REQUEST,
|
||||
|
@ -109,7 +110,12 @@ public final class LmsSetupTestResult {
|
|||
return new LmsSetupTestResult(lmsType);
|
||||
}
|
||||
|
||||
public static LmsSetupTestResult ofMissingAttributes(final LmsSetup.LmsType lmsType,
|
||||
public static LmsSetupTestResult ofAPINotSupported(final LmsSetup.LmsType lmsType) {
|
||||
return new LmsSetupTestResult(lmsType, new Error(ErrorType.TOKEN_REQUEST, "Not Supported"));
|
||||
}
|
||||
|
||||
public static LmsSetupTestResult ofMissingAttributes(
|
||||
final LmsSetup.LmsType lmsType,
|
||||
final Collection<APIMessage> attrs) {
|
||||
return new LmsSetupTestResult(lmsType, new Error(ErrorType.MISSING_ATTRIBUTE, "missing attribute(s)"), attrs);
|
||||
}
|
||||
|
|
|
@ -11,13 +11,14 @@ package ch.ethz.seb.sebserver.gbl.model.sebconfig;
|
|||
import javax.validation.constraints.NotNull;
|
||||
import javax.validation.constraints.Size;
|
||||
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION_NODE;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
|
||||
|
@ -72,6 +73,12 @@ public final class ConfigurationNode implements GrantEntity {
|
|||
@JsonProperty(CONFIGURATION_NODE.ATTR_STATUS)
|
||||
public final ConfigurationStatus status;
|
||||
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME)
|
||||
public final DateTime lastUpdateTime;
|
||||
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER)
|
||||
public final String lastUpdateUser;
|
||||
|
||||
@JsonCreator
|
||||
public ConfigurationNode(
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_ID) final Long id,
|
||||
|
@ -81,7 +88,9 @@ public final class ConfigurationNode implements GrantEntity {
|
|||
@JsonProperty(CONFIGURATION_NODE.ATTR_DESCRIPTION) final String description,
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_TYPE) final ConfigurationType type,
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_OWNER) final String owner,
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_STATUS) final ConfigurationStatus status) {
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_STATUS) final ConfigurationStatus status,
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME) final DateTime lastUpdateTime,
|
||||
@JsonProperty(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER) final String lastUpdateUser) {
|
||||
|
||||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
|
@ -91,24 +100,29 @@ public final class ConfigurationNode implements GrantEntity {
|
|||
this.type = (type != null) ? type : ConfigurationType.EXAM_CONFIG;
|
||||
this.owner = owner;
|
||||
this.status = status;
|
||||
this.lastUpdateTime = lastUpdateTime;
|
||||
this.lastUpdateUser = lastUpdateUser;
|
||||
}
|
||||
|
||||
public ConfigurationNode(final Long institutionId, final POSTMapper postParams) {
|
||||
this.id = null;
|
||||
this.institutionId = institutionId;
|
||||
final Long tplId = postParams.getLong(Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID);
|
||||
final Long tplId = postParams.getLong(CONFIGURATION_NODE.ATTR_TEMPLATE_ID);
|
||||
this.templateId = (tplId != null) ? tplId : DEFAULT_TEMPLATE_ID;
|
||||
this.name = postParams.getString(Domain.CONFIGURATION_NODE.ATTR_NAME);
|
||||
this.description = postParams.getString(Domain.CONFIGURATION_NODE.ATTR_DESCRIPTION);
|
||||
this.name = postParams.getString(CONFIGURATION_NODE.ATTR_NAME);
|
||||
this.description = postParams.getString(CONFIGURATION_NODE.ATTR_DESCRIPTION);
|
||||
this.type = postParams.getEnum(
|
||||
Domain.CONFIGURATION_NODE.ATTR_TYPE,
|
||||
CONFIGURATION_NODE.ATTR_TYPE,
|
||||
ConfigurationType.class,
|
||||
ConfigurationType.EXAM_CONFIG);
|
||||
this.owner = postParams.getString(Domain.CONFIGURATION_NODE.ATTR_OWNER);
|
||||
this.owner = postParams.getString(CONFIGURATION_NODE.ATTR_OWNER);
|
||||
this.status = postParams.getEnum(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
CONFIGURATION_NODE.ATTR_STATUS,
|
||||
ConfigurationStatus.class,
|
||||
ConfigurationStatus.CONSTRUCTION);
|
||||
this.lastUpdateTime = postParams.getDateTime(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME);
|
||||
this.lastUpdateUser = postParams.getString(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -158,6 +172,14 @@ public final class ConfigurationNode implements GrantEntity {
|
|||
return this.status;
|
||||
}
|
||||
|
||||
public DateTime getLastUpdateTime() {
|
||||
return this.lastUpdateTime;
|
||||
}
|
||||
|
||||
public String getLastUpdateUser() {
|
||||
return this.lastUpdateUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
@ -173,10 +195,12 @@ public final class ConfigurationNode implements GrantEntity {
|
|||
builder.append(this.description);
|
||||
builder.append(", type=");
|
||||
builder.append(this.type);
|
||||
builder.append(", owner=");
|
||||
builder.append(this.owner);
|
||||
builder.append(", status=");
|
||||
builder.append(this.status);
|
||||
builder.append(", lastUpdateTime=");
|
||||
builder.append(this.lastUpdateTime);
|
||||
builder.append(", lastUpdateUser=");
|
||||
builder.append(this.lastUpdateUser);
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
@ -190,7 +214,9 @@ public final class ConfigurationNode implements GrantEntity {
|
|||
null,
|
||||
ConfigurationType.EXAM_CONFIG,
|
||||
null,
|
||||
ConfigurationStatus.CONSTRUCTION);
|
||||
ConfigurationStatus.CONSTRUCTION,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
public static ConfigurationNode createNewTemplate(final Long institutionId) {
|
||||
|
@ -202,7 +228,9 @@ public final class ConfigurationNode implements GrantEntity {
|
|||
null,
|
||||
ConfigurationType.TEMPLATE,
|
||||
null,
|
||||
ConfigurationStatus.CONSTRUCTION);
|
||||
ConfigurationStatus.CONSTRUCTION,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
|||
import ch.ethz.seb.sebserver.gbl.api.POSTMapper;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Activatable;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain.CONFIGURATION_NODE;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain.SEB_CLIENT_CONFIGURATION;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
|
@ -168,6 +169,12 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE)
|
||||
public final Boolean active;
|
||||
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_TIME)
|
||||
public final DateTime lastUpdateTime;
|
||||
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER)
|
||||
public final String lastUpdateUser;
|
||||
|
||||
@JsonCreator
|
||||
public SEBClientConfig(
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ID) final Long id,
|
||||
|
@ -195,7 +202,9 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
@JsonProperty(ATTR_ENCRYPT_SECRET_CONFIRM) final CharSequence encryptSecretConfirm,
|
||||
@JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ALIAS) final String encryptCertificateAlias,
|
||||
@JsonProperty(ATTR_ENCRYPT_CERTIFICATE_ASYM) final Boolean encryptCertificateAsym,
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active) {
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_ACTIVE) final Boolean active,
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_TIME) final DateTime lastUpdateTime,
|
||||
@JsonProperty(SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER) final String lastUpdateUser) {
|
||||
|
||||
this.id = id;
|
||||
this.institutionId = institutionId;
|
||||
|
@ -229,6 +238,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
this.encryptCertificateAlias = encryptCertificateAlias;
|
||||
this.encryptCertificateAsym = encryptCertificateAsym;
|
||||
this.active = active;
|
||||
this.lastUpdateTime = lastUpdateTime;
|
||||
this.lastUpdateUser = lastUpdateUser;
|
||||
}
|
||||
|
||||
public SEBClientConfig(final Long institutionId, final POSTMapper postParams) {
|
||||
|
@ -268,6 +279,8 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
this.encryptCertificateAlias = postParams.getString(ATTR_ENCRYPT_CERTIFICATE_ALIAS);
|
||||
this.encryptCertificateAsym = postParams.getBooleanObject(ATTR_ENCRYPT_CERTIFICATE_ASYM);
|
||||
this.active = false;
|
||||
this.lastUpdateTime = postParams.getDateTime(CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME);
|
||||
this.lastUpdateUser = postParams.getString(CONFIGURATION_NODE.ATTR_LAST_UPDATE_USER);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -379,6 +392,38 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
return this.active;
|
||||
}
|
||||
|
||||
public Long getSebServerPingTime() {
|
||||
return this.sebServerPingTime;
|
||||
}
|
||||
|
||||
public VDIType getVdiType() {
|
||||
return this.vdiType;
|
||||
}
|
||||
|
||||
public String getVdiExecutable() {
|
||||
return this.vdiExecutable;
|
||||
}
|
||||
|
||||
public String getVdiPath() {
|
||||
return this.vdiPath;
|
||||
}
|
||||
|
||||
public String getVdiArguments() {
|
||||
return this.vdiArguments;
|
||||
}
|
||||
|
||||
public Boolean getEncryptCertificateAsym() {
|
||||
return this.encryptCertificateAsym;
|
||||
}
|
||||
|
||||
public DateTime getLastUpdateTime() {
|
||||
return this.lastUpdateTime;
|
||||
}
|
||||
|
||||
public String getLastUpdateUser() {
|
||||
return this.lastUpdateUser;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
|
@ -456,7 +501,9 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
Constants.EMPTY_NOTE,
|
||||
Constants.EMPTY_NOTE,
|
||||
this.encryptCertificateAsym,
|
||||
this.active);
|
||||
this.active,
|
||||
this.lastUpdateTime,
|
||||
this.lastUpdateUser);
|
||||
}
|
||||
|
||||
public static SEBClientConfig createNew(final Long institutionId) {
|
||||
|
@ -484,7 +531,9 @@ public final class SEBClientConfig implements GrantEntity, Activatable {
|
|||
null,
|
||||
null,
|
||||
false,
|
||||
false);
|
||||
false,
|
||||
null,
|
||||
null);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -8,8 +8,10 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gbl.model.session;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Predicate;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
|
@ -46,6 +48,11 @@ public final class ClientConnection implements GrantEntity {
|
|||
}
|
||||
}
|
||||
|
||||
public final static List<String> ACTIVE_STATES = Arrays.asList(
|
||||
ConnectionStatus.ACTIVE.name(),
|
||||
ConnectionStatus.AUTHENTICATED.name(),
|
||||
ConnectionStatus.CONNECTION_REQUESTED.name());
|
||||
|
||||
public static final ClientConnection EMPTY_CLIENT_CONNECTION = new ClientConnection(
|
||||
-1L, -1L, -1L,
|
||||
ConnectionStatus.UNDEFINED,
|
||||
|
@ -60,6 +67,7 @@ public final class ClientConnection implements GrantEntity {
|
|||
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;
|
||||
public static final String FILTER_ATTR_INFO = ATTR_INFO;
|
||||
public static final String FILTER_ATTR_TOKEN_LIST = "CONNECTION_TOKENS";
|
||||
|
||||
@JsonProperty(Domain.CLIENT_CONNECTION.ATTR_ID)
|
||||
public final Long id;
|
||||
|
@ -381,7 +389,7 @@ public final class ClientConnection implements GrantEntity {
|
|||
}
|
||||
|
||||
public static Predicate<ClientConnection> getStatusPredicate(final ConnectionStatus... status) {
|
||||
final EnumSet<ConnectionStatus> states = EnumSet.allOf(ConnectionStatus.class);
|
||||
final EnumSet<ConnectionStatus> states = EnumSet.noneOf(ConnectionStatus.class);
|
||||
if (status != null) {
|
||||
Collections.addAll(states, status);
|
||||
}
|
||||
|
|
|
@ -17,10 +17,14 @@ import com.fasterxml.jackson.annotation.JsonIgnore;
|
|||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.GrantEntity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
public class ClientConnectionData {
|
||||
public class ClientConnectionData implements GrantEntity {
|
||||
|
||||
public static final String ATTR_CLIENT_CONNECTION = "cData";
|
||||
public static final String ATTR_INDICATOR_VALUE = "iValues";
|
||||
|
@ -48,7 +52,7 @@ public class ClientConnectionData {
|
|||
this.indicatorValues = Utils.immutableListOf(indicatorValues);
|
||||
}
|
||||
|
||||
protected ClientConnectionData(
|
||||
public ClientConnectionData(
|
||||
final ClientConnection clientConnection,
|
||||
final List<? extends IndicatorValue> indicatorValues) {
|
||||
|
||||
|
@ -58,6 +62,26 @@ public class ClientConnectionData {
|
|||
this.indicatorValues = Utils.immutableListOf(indicatorValues);
|
||||
}
|
||||
|
||||
@Override
|
||||
public EntityType entityType() {
|
||||
return this.clientConnection.entityType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return this.clientConnection.getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getModelId() {
|
||||
return this.clientConnection.getModelId();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Long getInstitutionId() {
|
||||
return this.clientConnection.getInstitutionId();
|
||||
}
|
||||
|
||||
@JsonProperty(ATTR_MISSING_PING)
|
||||
public Boolean getMissingPing() {
|
||||
return this.missingPing;
|
||||
|
@ -78,6 +102,26 @@ public class ClientConnectionData {
|
|||
return this.missingPing || this.pendingNotification;
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public Double getIndicatorValue(final Long indicatorId) {
|
||||
return this.indicatorValues
|
||||
.stream()
|
||||
.filter(indicatorValue -> indicatorValue.getIndicatorId().equals(indicatorId))
|
||||
.findFirst()
|
||||
.map(iv -> iv.getValue())
|
||||
.orElse(Double.NaN);
|
||||
}
|
||||
|
||||
@JsonIgnore
|
||||
public String getIndicatorDisplayValue(final Indicator indicator) {
|
||||
return this.indicatorValues
|
||||
.stream()
|
||||
.filter(indicatorValue -> indicatorValue.getIndicatorId().equals(indicator.id))
|
||||
.findFirst()
|
||||
.map(iv -> IndicatorValue.getDisplayValue(iv, indicator.type))
|
||||
.orElse(Constants.EMPTY_NOTE);
|
||||
}
|
||||
|
||||
public ClientConnection getClientConnection() {
|
||||
return this.clientConnection;
|
||||
}
|
||||
|
|
|
@ -27,7 +27,8 @@ public final class ClientInstruction {
|
|||
SEB_QUIT,
|
||||
SEB_PROCTORING,
|
||||
SEB_RECONFIGURE_SETTINGS,
|
||||
NOTIFICATION_CONFIRM
|
||||
NOTIFICATION_CONFIRM,
|
||||
SEB_FORCE_LOCK_SCREEN
|
||||
}
|
||||
|
||||
public enum ProctoringInstructionMethod {
|
||||
|
@ -70,6 +71,11 @@ public final class ClientInstruction {
|
|||
public static final String ZOOM_RECEIVE_VIDEO = "zoomReceiveVideo";
|
||||
public static final String ZOOM_ALLOW_CHAT = "zoomFeatureFlagChat";
|
||||
}
|
||||
|
||||
public interface SEB_FORCE_LOCK_SCREEN {
|
||||
public static final String MESSAGE = "message";
|
||||
public static final String IMAGE_URL = "imageURL";
|
||||
}
|
||||
}
|
||||
|
||||
@JsonProperty(Domain.CLIENT_INSTRUCTION.ATTR_ID)
|
||||
|
|
|
@ -32,7 +32,7 @@ public interface IndicatorValue extends IndicatorValueHolder {
|
|||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
if (type.integerValue) {
|
||||
return String.valueOf((int) indicatorValue.getValue());
|
||||
return String.valueOf((long) indicatorValue.getValue());
|
||||
} else {
|
||||
return String.valueOf(indicatorValue.getValue());
|
||||
}
|
||||
|
|
|
@ -11,6 +11,7 @@ package ch.ethz.seb.sebserver.gbl.model.session;
|
|||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup.LmsType;
|
||||
|
||||
|
@ -46,8 +47,8 @@ public final class RunningExamInfo {
|
|||
public RunningExamInfo(final Exam exam, final LmsType lmsType) {
|
||||
this.examId = exam.getModelId();
|
||||
this.name = exam.name;
|
||||
this.url = exam.startURL;
|
||||
this.lmsType = (lmsType == null) ? "" : lmsType.name();
|
||||
this.url = exam.getStartURL();
|
||||
this.lmsType = (lmsType == null) ? Constants.EMPTY_NOTE : lmsType.name();
|
||||
}
|
||||
|
||||
public String getExamId() {
|
||||
|
|
|
@ -18,6 +18,7 @@ public enum UserLogActivityType {
|
|||
PASSWORD_CHANGE,
|
||||
DEACTIVATE,
|
||||
ACTIVATE,
|
||||
FINISHED,
|
||||
DELETE,
|
||||
LOGIN,
|
||||
LOGOUT
|
||||
|
|
|
@ -262,7 +262,7 @@ public final class Result<T> {
|
|||
|
||||
public Result<T> onErrorDo(final Function<Exception, T> errorHandler) {
|
||||
if (this.error != null) {
|
||||
return new Result<>(errorHandler.apply(this.error));
|
||||
return Result.tryCatch(() -> errorHandler.apply(this.error));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
|
|
@ -50,9 +50,11 @@ import org.springframework.util.LinkedMultiValueMap;
|
|||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.JSONMapper;
|
||||
|
||||
public final class Utils {
|
||||
|
||||
|
@ -246,6 +248,9 @@ public final class Utils {
|
|||
}
|
||||
|
||||
public static String formatDate(final DateTime dateTime) {
|
||||
if (dateTime == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
return dateTime.toString(Constants.STANDARD_DATE_TIME_MILLIS_FORMATTER);
|
||||
}
|
||||
|
||||
|
@ -576,6 +581,9 @@ public final class Utils {
|
|||
* @param rgb foreground or text color
|
||||
* @return true of the background color for given foreground color shall be dark or false if it shall be light */
|
||||
public static boolean darkColorContrast(final RGB rgb) {
|
||||
if (rgb == null) {
|
||||
return true;
|
||||
}
|
||||
return rgb.red + rgb.green + rgb.blue > DARK_COLOR_THRESHOLD;
|
||||
}
|
||||
|
||||
|
@ -590,15 +598,16 @@ public final class Utils {
|
|||
}
|
||||
|
||||
public static RGB parseRGB(final String colorString) {
|
||||
if (StringUtils.isBlank(colorString)) {
|
||||
try {
|
||||
|
||||
final int r = Integer.parseInt(colorString.substring(0, 2), 16);
|
||||
final int g = Integer.parseInt(colorString.substring(2, 4), 16);
|
||||
final int b = Integer.parseInt(colorString.substring(4, 6), 16);
|
||||
|
||||
return new RGB(r, g, b);
|
||||
} catch (final Exception e) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final int r = Integer.parseInt(colorString.substring(0, 2), 16);
|
||||
final int g = Integer.parseInt(colorString.substring(2, 4), 16);
|
||||
final int b = Integer.parseInt(colorString.substring(4, 6), 16);
|
||||
|
||||
return new RGB(r, g, b);
|
||||
}
|
||||
|
||||
public static String toColorFractionString(final int fraction) {
|
||||
|
@ -735,4 +744,17 @@ public final class Utils {
|
|||
return builder;
|
||||
}
|
||||
|
||||
public static Map<String, String> jsonToMap(final String attribute, final JSONMapper mapper) {
|
||||
if (StringUtils.isBlank(attribute)) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
try {
|
||||
return mapper.readValue(attribute, new TypeReference<Map<String, String>>() {
|
||||
});
|
||||
} catch (final Exception e) {
|
||||
log.error("Failed to parse json to map: ", e);
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
|||
@GuiProfile
|
||||
public class GuiServiceInfo {
|
||||
|
||||
private final String sebServerVersion;
|
||||
private final String externalScheme;
|
||||
private final String internalServer;
|
||||
private final String externalServer;
|
||||
|
@ -29,8 +30,10 @@ public class GuiServiceInfo {
|
|||
private final UriComponentsBuilder internalServerURIBuilder;
|
||||
private final UriComponentsBuilder externalServerURIBuilder;
|
||||
private final boolean distributedSetup;
|
||||
private final boolean multilingualGUI;
|
||||
|
||||
public GuiServiceInfo(
|
||||
@Value("${sebserver.version:--}") final String sebServerVersion,
|
||||
@Value("${server.address}") final String internalServer,
|
||||
@Value("${server.port}") final String internalPort,
|
||||
@Value("${sebserver.gui.http.external.scheme}") final String externalScheme,
|
||||
|
@ -38,7 +41,8 @@ public class GuiServiceInfo {
|
|||
@Value("${sebserver.gui.http.external.port}") final String externalPort,
|
||||
@Value("${sebserver.gui.entrypoint:/gui}") final String entryPoint,
|
||||
@Value("${server.servlet.context-path:/}") final String contextPath,
|
||||
@Value("${sebserver.webservice.distributed:false}") final boolean distributedSetup) {
|
||||
@Value("${sebserver.webservice.distributed:false}") final boolean distributedSetup,
|
||||
@Value("${sebserver.gui.multilingual:false}") final boolean multilingualGUI) {
|
||||
|
||||
if (StringUtils.isBlank(externalScheme)) {
|
||||
throw new RuntimeException("Missing mandatory inital parameter sebserver.gui.http.external.servername");
|
||||
|
@ -48,6 +52,7 @@ public class GuiServiceInfo {
|
|||
throw new RuntimeException("Missing mandatory inital parameter sebserver.gui.http.external.servername");
|
||||
}
|
||||
|
||||
this.sebServerVersion = sebServerVersion;
|
||||
this.externalScheme = externalScheme;
|
||||
this.internalServer = internalServer;
|
||||
this.externalServer = externalServer;
|
||||
|
@ -73,6 +78,7 @@ public class GuiServiceInfo {
|
|||
}
|
||||
|
||||
this.distributedSetup = distributedSetup;
|
||||
this.multilingualGUI = multilingualGUI;
|
||||
}
|
||||
|
||||
public String getExternalScheme() {
|
||||
|
@ -115,4 +121,12 @@ public class GuiServiceInfo {
|
|||
return this.distributedSetup;
|
||||
}
|
||||
|
||||
public String getSebServerVersion() {
|
||||
return this.sebServerVersion;
|
||||
}
|
||||
|
||||
public boolean isMultilingualGUI() {
|
||||
return this.multilingualGUI;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,8 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
private String remoteProctoringEndpoint;
|
||||
@Value("${sebserver.gui.remote.proctoring.api-servler.endpoint:/remote-view-servlet}")
|
||||
private String remoteProctoringViewServletEndpoint;
|
||||
@Value("${springdoc.api-docs.enabled:false}")
|
||||
private boolean springDocsAPIEnabled;
|
||||
|
||||
/** Gui-service related public URLS from spring web security perspective */
|
||||
public static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher(
|
||||
|
@ -57,6 +59,10 @@ public class GuiWebsecurityConfig extends WebSecurityConfigurerAdapter {
|
|||
.antMatchers(this.guiEntryPoint)
|
||||
.antMatchers(this.remoteProctoringEndpoint)
|
||||
.antMatchers(this.remoteProctoringEndpoint + this.remoteProctoringViewServletEndpoint + "/*");
|
||||
|
||||
if (this.springDocsAPIEnabled) {
|
||||
web.ignoring().antMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs/**");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -231,7 +231,7 @@ public final class InstitutionalAuthenticationEntryPoint implements Authenticati
|
|||
final Object attribute = RWT.getUISession().getHttpSession().getAttribute(INST_SUFFIX_ATTRIBUTE);
|
||||
return (attribute != null) ? String.valueOf(attribute) : null;
|
||||
} catch (final Exception e) {
|
||||
log.warn("Failed to extract institutional endpoint form user session: {}", e.getMessage());
|
||||
log.warn("Failed to extract institutional endpoint from user session: {}", e.getMessage());
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -30,12 +30,20 @@ public enum ActionCategory {
|
|||
EXAM_MONITORING_NOTIFICATION_LIST(new LocTextKey(
|
||||
"sebserver.monitoring.exam.connection.notificationlist.actions"),
|
||||
1),
|
||||
EXAM_MONITORING_2(new LocTextKey(
|
||||
"sebserver.monitoring.exam.connection.actions.group2"),
|
||||
2),
|
||||
EXAM_MONITORING_3(new LocTextKey(
|
||||
"sebserver.monitoring.exam.connection.actions.group3"),
|
||||
3),
|
||||
CLIENT_EVENT_LIST(new LocTextKey("sebserver.monitoring.exam.connection.list.actions"), 1),
|
||||
LOGS_USER_ACTIVITY_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),
|
||||
LOGS_SEB_CLIENT_LIST(new LocTextKey("sebserver.userlogs.list.actions"), 1),
|
||||
VARIA(new LocTextKey("sebserver.overall.action.category.varia"), 0),
|
||||
FILTER(new LocTextKey("sebserver.exam.monitoring.action.category.filter"), 50),
|
||||
PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60);
|
||||
PROCTORING(new LocTextKey("sebserver.exam.overall.action.category.proctoring"), 60),
|
||||
|
||||
FINISHED_EXAM_LIST(new LocTextKey("sebserver.finished.exam.list.actions"), 1);
|
||||
|
||||
public final LocTextKey title;
|
||||
public final int slotPosition;
|
||||
|
|
|
@ -73,6 +73,11 @@ public enum ActionDefinition {
|
|||
ImageIcon.SWITCH,
|
||||
PageStateDefinitionImpl.INSTITUTION_LIST,
|
||||
ActionCategory.INSTITUTION_LIST),
|
||||
INSTITUTION_DELETE(
|
||||
new LocTextKey("sebserver.institution.action.delete"),
|
||||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.INSTITUTION_VIEW,
|
||||
ActionCategory.FORM),
|
||||
|
||||
USER_ACCOUNT_VIEW_LIST(
|
||||
new LocTextKey("sebserver.useraccount.action.list"),
|
||||
|
@ -215,6 +220,11 @@ public enum ActionDefinition {
|
|||
ImageIcon.SWITCH,
|
||||
PageStateDefinitionImpl.LMS_SETUP_LIST,
|
||||
ActionCategory.LMS_SETUP_LIST),
|
||||
LMS_SETUP_DELETE(
|
||||
new LocTextKey("sebserver.lmssetup.action.delete"),
|
||||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.LMS_SETUP_VIEW,
|
||||
ActionCategory.FORM),
|
||||
|
||||
QUIZ_DISCOVERY_VIEW_LIST(
|
||||
new LocTextKey("sebserver.quizdiscovery.action.list"),
|
||||
|
@ -281,6 +291,11 @@ public enum ActionDefinition {
|
|||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.EXAM_VIEW,
|
||||
ActionCategory.FORM),
|
||||
EXAM_ARCHIVE(
|
||||
new LocTextKey("sebserver.exam.action.archive"),
|
||||
ImageIcon.ARCHIVE,
|
||||
PageStateDefinitionImpl.EXAM_VIEW,
|
||||
ActionCategory.FORM),
|
||||
|
||||
EXAM_MODIFY_SEB_RESTRICTION_DETAILS(
|
||||
new LocTextKey("sebserver.exam.action.sebrestriction.details"),
|
||||
|
@ -417,6 +432,16 @@ public enum ActionDefinition {
|
|||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.EXAM_TEMPLATE_LIST,
|
||||
ActionCategory.FORM),
|
||||
EXAM_TEMPLATE_PROCTORING_ON(
|
||||
new LocTextKey("sebserver.examtemplate.proctoring.actions.open"),
|
||||
ImageIcon.VISIBILITY,
|
||||
PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW,
|
||||
ActionCategory.FORM),
|
||||
EXAM_TEMPLATE_PROCTORING_OFF(
|
||||
new LocTextKey("sebserver.examtemplate.proctoring.actions.open"),
|
||||
ImageIcon.VISIBILITY_OFF,
|
||||
PageStateDefinitionImpl.EXAM_TEMPLATE_VIEW,
|
||||
ActionCategory.FORM),
|
||||
|
||||
INDICATOR_TEMPLATE_NEW(
|
||||
new LocTextKey("sebserver.examtemplate.indicator.action.list.new"),
|
||||
|
@ -502,6 +527,16 @@ public enum ActionDefinition {
|
|||
ImageIcon.EXPORT,
|
||||
PageStateDefinitionImpl.SEB_CLIENT_CONFIG_VIEW,
|
||||
ActionCategory.FORM),
|
||||
SEB_CLIENT_CONFIG_DELETE(
|
||||
new LocTextKey("sebserver.clientconfig.action.delete"),
|
||||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.SEB_CLIENT_CONFIG_LIST,
|
||||
ActionCategory.FORM),
|
||||
SEB_CLIENT_CONFIG_SHOW_CREDENTIALS(
|
||||
new LocTextKey("sebserver.clientconfig.action.credentials"),
|
||||
ImageIcon.SECURE,
|
||||
PageStateDefinitionImpl.SEB_CLIENT_CONFIG_VIEW,
|
||||
ActionCategory.FORM),
|
||||
|
||||
SEB_EXAM_CONFIG_LIST(
|
||||
new LocTextKey("sebserver.examconfig.action.list"),
|
||||
|
@ -527,6 +562,18 @@ public enum ActionDefinition {
|
|||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST,
|
||||
ActionCategory.LIST_VARIA),
|
||||
|
||||
SEB_EXAM_CONFIG_BULK_STATE_CHANGE(
|
||||
new LocTextKey("sebserver.examconfig.list.action.statechange"),
|
||||
ImageIcon.SWITCH,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST,
|
||||
ActionCategory.SEB_EXAM_CONFIG_LIST),
|
||||
|
||||
SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE(
|
||||
new LocTextKey("sebserver.examconfig.list.action.reset"),
|
||||
ImageIcon.EXPORT,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_LIST,
|
||||
ActionCategory.SEB_EXAM_CONFIG_LIST),
|
||||
|
||||
SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST(
|
||||
new LocTextKey("sebserver.examconfig.action.list.modify.properties"),
|
||||
ImageIcon.EDIT,
|
||||
|
@ -580,8 +627,12 @@ public enum ActionDefinition {
|
|||
SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST(
|
||||
new LocTextKey("sebserver.examconfig.action.copy"),
|
||||
ImageIcon.COPY,
|
||||
// PageStateDefinitionImpl.SEB_EXAM_CONFIG_PROP_EDIT,
|
||||
ActionCategory.SEB_EXAM_CONFIG_LIST),
|
||||
|
||||
SEB_EXAM_CONFIG_RESET_TO_TEMPLATE_SETTINGS(
|
||||
new LocTextKey("sebserver.examconfig.action.restore.template.settings"),
|
||||
ImageIcon.EXPORT,
|
||||
ActionCategory.FORM),
|
||||
SEB_EXAM_CONFIG_COPY_CONFIG(
|
||||
new LocTextKey("sebserver.examconfig.action.copy"),
|
||||
ImageIcon.COPY,
|
||||
|
@ -644,6 +695,11 @@ public enum ActionDefinition {
|
|||
ImageIcon.SAVE,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_VIEW,
|
||||
ActionCategory.FORM),
|
||||
SEB_EXAM_CONFIG_TEMPLATE_DELETE(
|
||||
new LocTextKey("sebserver.configtemplate.action.delete"),
|
||||
ImageIcon.DELETE,
|
||||
PageStateDefinitionImpl.SEB_EXAM_CONFIG_TEMPLATE_LIST,
|
||||
ActionCategory.FORM),
|
||||
|
||||
SEB_EXAM_CONFIG_TEMPLATE_ATTR_EDIT(
|
||||
new LocTextKey("sebserver.configtemplate.attr.list.actions.modify"),
|
||||
|
@ -713,12 +769,18 @@ public enum ActionDefinition {
|
|||
new LocTextKey("sebserver.monitoring.exam.connection.action.view"),
|
||||
ImageIcon.SHOW,
|
||||
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
|
||||
ActionCategory.CLIENT_EVENT_LIST),
|
||||
ActionCategory.EXAM_MONITORING_3),
|
||||
MONITOR_EXAM_CLIENT_CONNECTION_QUIT(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit"),
|
||||
ImageIcon.SEND_QUIT,
|
||||
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
|
||||
ActionCategory.FORM),
|
||||
MONITOR_EXAM_CLIENT_CONNECTION_LOCK(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.lock"),
|
||||
ImageIcon.LOCK,
|
||||
PageStateDefinitionImpl.MONITORING_CLIENT_CONNECTION,
|
||||
ActionCategory.FORM),
|
||||
|
||||
MONITOR_EXAM_CLIENT_CONNECTION_PROCTORING(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.proctoring"),
|
||||
ImageIcon.PROCTOR_SINGLE,
|
||||
|
@ -739,12 +801,18 @@ public enum ActionDefinition {
|
|||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.selected"),
|
||||
ImageIcon.SEND_QUIT,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.CLIENT_EVENT_LIST),
|
||||
ActionCategory.EXAM_MONITORING_2),
|
||||
MONITOR_EXAM_QUIT_ALL(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.all"),
|
||||
ImageIcon.SEND_QUIT,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.FORM),
|
||||
ActionCategory.EXAM_MONITORING_2),
|
||||
MONITOR_EXAM_LOCK_SELECTED(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.lock.selected"),
|
||||
ImageIcon.LOCK,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.EXAM_MONITORING_2),
|
||||
|
||||
MONITOR_EXAM_BACK_TO_OVERVIEW(
|
||||
new LocTextKey("sebserver.monitoring.exam.action.detail.view"),
|
||||
ImageIcon.SHOW,
|
||||
|
@ -755,7 +823,7 @@ public enum ActionDefinition {
|
|||
new LocTextKey("sebserver.monitoring.exam.connection.action.disable"),
|
||||
ImageIcon.DISABLE,
|
||||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.CLIENT_EVENT_LIST),
|
||||
ActionCategory.EXAM_MONITORING_3),
|
||||
|
||||
MONITOR_EXAM_HIDE_REQUESTED_CONNECTION(
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.hide.requested"),
|
||||
|
@ -832,6 +900,25 @@ public enum ActionDefinition {
|
|||
PageStateDefinitionImpl.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.PROCTORING),
|
||||
|
||||
FINISHED_EXAM_VIEW_LIST(
|
||||
new LocTextKey("sebserver.finished.action.list"),
|
||||
PageStateDefinitionImpl.FINISHED_EXAM_LIST),
|
||||
VIEW_FINISHED_EXAM_FROM_LIST(
|
||||
new LocTextKey("sebserver.finished.exam.action.list.view"),
|
||||
ImageIcon.SHOW,
|
||||
PageStateDefinitionImpl.FINISHED_EXAM,
|
||||
ActionCategory.FINISHED_EXAM_LIST),
|
||||
VIEW_FINISHED_EXAM_CLIENT_CONNECTION(
|
||||
new LocTextKey("sebserver.finished.exam.connection.action.view"),
|
||||
ImageIcon.SHOW,
|
||||
PageStateDefinitionImpl.FINISHED_CLIENT_CONNECTION,
|
||||
ActionCategory.CLIENT_EVENT_LIST),
|
||||
FINISHED_EXAM_BACK_TO_OVERVIEW(
|
||||
new LocTextKey("sebserver.finished.exam.action.detail.view"),
|
||||
ImageIcon.SHOW,
|
||||
PageStateDefinitionImpl.FINISHED_EXAM,
|
||||
ActionCategory.FORM),
|
||||
|
||||
LOGS_USER_ACTIVITY_LIST(
|
||||
new LocTextKey("sebserver.logs.activity.userlogs"),
|
||||
PageStateDefinitionImpl.USER_ACTIVITY_LOGS),
|
||||
|
|
|
@ -238,6 +238,9 @@ public class ActionPane implements TemplateComposer {
|
|||
CustomVariant.TEXT_H3,
|
||||
category.title);
|
||||
final GridData titleLayout = new GridData(SWT.FILL, SWT.TOP, true, false);
|
||||
if (" ".equals(this.pageService.getI18nSupport().getText(category.title))) {
|
||||
titleLayout.heightHint = 6;
|
||||
}
|
||||
actionsTitle.setLayoutData(titleLayout);
|
||||
}
|
||||
|
||||
|
|
|
@ -339,14 +339,25 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
|
||||
// Monitoring exams
|
||||
if (isSupporter) {
|
||||
final TreeItem clientConfig = this.widgetFactory.treeItemLocalized(
|
||||
|
||||
final TreeItem monitoringExams = this.widgetFactory.treeItemLocalized(
|
||||
monitoring,
|
||||
ActivityDefinition.MONITORING_EXAMS.displayName);
|
||||
injectActivitySelection(
|
||||
clientConfig,
|
||||
monitoringExams,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.RUNNING_EXAM_VIEW_LIST)
|
||||
.create());
|
||||
|
||||
final TreeItem clientConfig = this.widgetFactory.treeItemLocalized(
|
||||
monitoring,
|
||||
ActivityDefinition.FINISHED_EXAMS.displayName);
|
||||
injectActivitySelection(
|
||||
clientConfig,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.FINISHED_EXAM_VIEW_LIST)
|
||||
.create());
|
||||
|
||||
}
|
||||
|
||||
// SEB Client Logs
|
||||
|
|
|
@ -28,6 +28,7 @@ public enum ActivityDefinition implements Activity {
|
|||
SEB_CERTIFICATE_MANAGEMENT(new LocTextKey("sebserver.certificate.action.list")),
|
||||
MONITORING(new LocTextKey("sebserver.overall.activity.title.monitoring")),
|
||||
MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list")),
|
||||
FINISHED_EXAMS(new LocTextKey("sebserver.finished.action.list")),
|
||||
SEB_CLIENT_LOGS(new LocTextKey("sebserver.logs.activity.seblogs"));
|
||||
|
||||
public final LocTextKey displayName;
|
||||
|
|
|
@ -33,6 +33,9 @@ import ch.ethz.seb.sebserver.gui.content.exam.IndicatorTemplateForm;
|
|||
import ch.ethz.seb.sebserver.gui.content.exam.LmsSetupForm;
|
||||
import ch.ethz.seb.sebserver.gui.content.exam.LmsSetupList;
|
||||
import ch.ethz.seb.sebserver.gui.content.exam.QuizLookupList;
|
||||
import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExam;
|
||||
import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExamClientConnection;
|
||||
import ch.ethz.seb.sebserver.gui.content.monitoring.FinishedExamList;
|
||||
import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringClientConnection;
|
||||
import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringRunningExam;
|
||||
import ch.ethz.seb.sebserver.gui.content.monitoring.MonitoringRunningExamList;
|
||||
|
@ -96,6 +99,10 @@ public enum PageStateDefinitionImpl implements PageStateDefinition {
|
|||
MONITORING_RUNNING_EXAM(Type.FORM_VIEW, MonitoringRunningExam.class, ActivityDefinition.MONITORING_EXAMS),
|
||||
MONITORING_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.MONITORING_EXAMS),
|
||||
|
||||
FINISHED_EXAM_LIST(Type.LIST_VIEW, FinishedExamList.class, ActivityDefinition.FINISHED_EXAMS),
|
||||
FINISHED_EXAM(Type.FORM_VIEW, FinishedExam.class, ActivityDefinition.FINISHED_EXAMS),
|
||||
FINISHED_CLIENT_CONNECTION(Type.FORM_VIEW, FinishedExamClientConnection.class, ActivityDefinition.FINISHED_EXAMS),
|
||||
|
||||
USER_ACTIVITY_LOGS(Type.LIST_VIEW, UserActivityLogs.class, ActivityDefinition.USER_ACTIVITY_LOGS),
|
||||
SEB_CLIENT_LOGS(Type.LIST_VIEW, SEBClientEvents.class, ActivityDefinition.SEB_CLIENT_LOGS)
|
||||
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* 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.admin;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityDependency;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.Institution;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.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.PageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputWizard;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputWizard.WizardAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputWizard.WizardPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.DeleteInstitution;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitution;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.institution.GetInstitutionDependency;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class InstitutionDeletePopup {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(InstitutionDeletePopup.class);
|
||||
|
||||
private final static LocTextKey FORM_TITLE =
|
||||
new LocTextKey("sebserver.institution.delete.form.title");
|
||||
private final static LocTextKey FORM_INFO =
|
||||
new LocTextKey("sebserver.institution.delete.form.info");
|
||||
private final static LocTextKey FORM_REPORT_INFO =
|
||||
new LocTextKey("sebserver.institution.delete.report.info");
|
||||
private final static LocTextKey FORM_REPORT_LIST_TYPE =
|
||||
new LocTextKey("sebserver.institution.delete.report.list.type");
|
||||
private final static LocTextKey FORM_REPORT_LIST_NAME =
|
||||
new LocTextKey("sebserver.institution.delete.report.list.name");
|
||||
private final static LocTextKey FORM_REPORT_LIST_DESC =
|
||||
new LocTextKey("sebserver.institution.delete.report.list.description");
|
||||
private final static LocTextKey FORM_REPORT_NONE =
|
||||
new LocTextKey("sebserver.institution.delete.report.list.empty");
|
||||
|
||||
private final static LocTextKey ACTION_DELETE =
|
||||
new LocTextKey("sebserver.institution.delete.action.delete");
|
||||
|
||||
private final static LocTextKey DELETE_CONFIRM_TITLE =
|
||||
new LocTextKey("sebserver.institution.delete.confirm.title");
|
||||
private final static LocTextKey DELETE_ERROR_CONSISTENCY =
|
||||
new LocTextKey("sebserver.institution.action.delete.consistency.error");
|
||||
|
||||
private final PageService pageService;
|
||||
|
||||
protected InstitutionDeletePopup(final PageService pageService) {
|
||||
this.pageService = pageService;
|
||||
}
|
||||
|
||||
public Function<PageAction, PageAction> deleteWizardFunction(final PageContext pageContext) {
|
||||
return action -> {
|
||||
|
||||
final ModalInputWizard<PageContext> wizard =
|
||||
new ModalInputWizard<PageContext>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.pageService.getWidgetFactory())
|
||||
.setVeryLargeDialogWidth();
|
||||
|
||||
final String page1Id = "DELETE_PAGE";
|
||||
final Predicate<PageContext> callback = pc -> doDelete(this.pageService, pc);
|
||||
final BiFunction<PageContext, Composite, Supplier<PageContext>> composePage1 =
|
||||
(prefPageContext, content) -> composeDeleteDialog(content,
|
||||
(prefPageContext != null) ? prefPageContext : pageContext);
|
||||
|
||||
final WizardPage<PageContext> page1 = new WizardPage<>(
|
||||
page1Id,
|
||||
true,
|
||||
composePage1,
|
||||
new WizardAction<>(ACTION_DELETE, callback));
|
||||
|
||||
wizard.open(FORM_TITLE, Utils.EMPTY_EXECUTION, page1);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean doDelete(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
try {
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final Institution toDelete = this.pageService
|
||||
.getRestService()
|
||||
.getBuilder(GetInstitution.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final RestCall<EntityProcessingReport>.RestCallBuilder restCallBuilder = this.pageService.getRestService()
|
||||
.getBuilder(DeleteInstitution.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name());
|
||||
|
||||
final Result<EntityProcessingReport> deleteCall = restCallBuilder.call();
|
||||
if (deleteCall.hasError()) {
|
||||
final Exception error = deleteCall.getError();
|
||||
if (error instanceof RestCallError) {
|
||||
final APIMessage message = ((RestCallError) error)
|
||||
.getAPIMessages()
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (message != null && ErrorMessage.INTEGRITY_VALIDATION.isOf(message)) {
|
||||
pageContext.publishPageMessage(new PageMessageException(DELETE_ERROR_CONSISTENCY));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final EntityProcessingReport report = deleteCall.getOrThrow();
|
||||
|
||||
final PageAction action = this.pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.INSTITUTION_VIEW_LIST)
|
||||
.create();
|
||||
|
||||
this.pageService.firePageEvent(
|
||||
new ActionEvent(action),
|
||||
action.pageContext());
|
||||
|
||||
final List<EntityKey> dependencies = report.results.stream()
|
||||
.filter(key -> !key.equals(entityKey))
|
||||
.collect(Collectors.toList());
|
||||
pageContext.publishPageMessage(
|
||||
DELETE_CONFIRM_TITLE,
|
||||
new LocTextKey(
|
||||
"sebserver.institution.delete.confirm.message",
|
||||
toDelete.toName().name,
|
||||
dependencies.size(),
|
||||
(report.errors.isEmpty()) ? "no" : String.valueOf((report.errors.size()))));
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while trying to delete Institution:", e);
|
||||
pageContext.notifyUnexpectedError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<PageContext> composeDeleteDialog(
|
||||
final Composite parent,
|
||||
final PageContext pageContext) {
|
||||
|
||||
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
|
||||
final Composite grid = this.pageService.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final Label title = this.pageService.getWidgetFactory()
|
||||
.labelLocalized(grid, CustomVariant.TEXT_H3, FORM_INFO);
|
||||
final GridData gridData = new GridData();
|
||||
gridData.horizontalIndent = 10;
|
||||
gridData.verticalIndent = 10;
|
||||
title.setLayoutData(gridData);
|
||||
|
||||
final Label titleReport = this.pageService.getWidgetFactory()
|
||||
.labelLocalized(grid, CustomVariant.TEXT_H3, FORM_REPORT_INFO);
|
||||
final GridData gridDataReport = new GridData();
|
||||
gridDataReport.horizontalIndent = 10;
|
||||
gridDataReport.verticalIndent = 10;
|
||||
titleReport.setLayoutData(gridDataReport);
|
||||
|
||||
try {
|
||||
|
||||
// get dependencies
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final RestCall<Set<EntityDependency>>.RestCallBuilder restCallBuilder = this.pageService.getRestService()
|
||||
.getBuilder(GetInstitutionDependency.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name());
|
||||
|
||||
final Set<EntityDependency> dependencies = restCallBuilder
|
||||
.call()
|
||||
.getOrThrow();
|
||||
final List<EntityDependency> list = dependencies
|
||||
.stream()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
this.pageService.<EntityDependency> staticListTableBuilder(list, null)
|
||||
.withEmptyMessage(FORM_REPORT_NONE)
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
"FORM_REPORT_LIST_TYPE",
|
||||
FORM_REPORT_LIST_TYPE,
|
||||
dep -> i18nSupport
|
||||
.getText("sebserver.overall.types.entityType." + dep.self.entityType.name())))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
"FORM_REPORT_LIST_NAME",
|
||||
FORM_REPORT_LIST_NAME,
|
||||
dep -> dep.name))
|
||||
.withColumn(new ColumnDefinition<EntityDependency>(
|
||||
"FORM_REPORT_LIST_DESC",
|
||||
FORM_REPORT_LIST_DESC,
|
||||
dep -> dep.description))
|
||||
.compose(pageContext.copyOf(grid));
|
||||
|
||||
return () -> pageContext;
|
||||
} catch (final Exception e) {
|
||||
log.error("Error while trying to compose Institution delete report page: ", e);
|
||||
pageContext.notifyUnexpectedError(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -56,12 +56,16 @@ public class InstitutionForm implements TemplateComposer {
|
|||
private final PageService pageService;
|
||||
private final RestService restService;
|
||||
private final CurrentUser currentUser;
|
||||
private final InstitutionDeletePopup institutionDeletePopup;
|
||||
|
||||
protected InstitutionForm(final PageService pageService) {
|
||||
protected InstitutionForm(
|
||||
final PageService pageService,
|
||||
final InstitutionDeletePopup institutionDeletePopup) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.restService = pageService.getRestService();
|
||||
this.currentUser = pageService.getCurrentUser();
|
||||
this.institutionDeletePopup = institutionDeletePopup;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -132,6 +136,11 @@ public class InstitutionForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.INSTITUTION_DELETE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.institutionDeletePopup.deleteWizardFunction(pageContext))
|
||||
.publishIf(() -> writeGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.INSTITUTION_DEACTIVATE)
|
||||
.withEntityKey(entityKey)
|
||||
.withSimpleRestCall(this.restService, DeactivateInstitution.class)
|
||||
|
|
|
@ -138,14 +138,14 @@ public class InstitutionList implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.INSTITUTION_VIEW_FROM_LIST)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.INSTITUTION_MODIFY_FROM_LIST)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> instGrant.m(), false)
|
||||
|
|
|
@ -54,6 +54,8 @@ public class UserAccountChangePasswordForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.useraccount.form.password.new.confirm");
|
||||
private static final LocTextKey FORM_PASSWORD_TEXT_KEY =
|
||||
new LocTextKey("sebserver.useraccount.form.password");
|
||||
private static final String PASSWORD_CHANGE_INFO_KEY =
|
||||
"sebserver.useraccount.form.password.info";
|
||||
|
||||
private final PageService pageService;
|
||||
private final RestService restService;
|
||||
|
@ -85,6 +87,11 @@ public class UserAccountChangePasswordForm implements TemplateComposer {
|
|||
pageContext.getParent(),
|
||||
new LocTextKey(FORM_TITLE_KEY, userInfo.username));
|
||||
|
||||
widgetFactory.labelLocalized(
|
||||
content,
|
||||
WidgetFactory.CustomVariant.SUBTITLE,
|
||||
new LocTextKey(PASSWORD_CHANGE_INFO_KEY, userInfo.username));
|
||||
|
||||
final boolean ownAccount = this.currentUser.get().uuid.equals(entityKey.getModelId());
|
||||
|
||||
// The Password Change form
|
||||
|
|
|
@ -222,11 +222,12 @@ public class UserAccountList implements TemplateComposer {
|
|||
.publishIf(userGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_MODIFY_FROM_LIST)
|
||||
.withSelect(table::getSelection, this::editAction, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, this::editAction, EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_TOGGLE_ACTIVITY)
|
||||
|
|
|
@ -244,7 +244,7 @@ public class UserActivityLogs implements TemplateComposer {
|
|||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
action -> this.showDetails(action, table.getSingleSelectedROWData()),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.noEventPropagation()
|
||||
|
|
|
@ -227,10 +227,6 @@ public class CertificateImportPopup {
|
|||
null,
|
||||
CertificateFileType.getAllExtensions()))
|
||||
|
||||
// .addField(FormBuilder.text(
|
||||
// CertificateInfo.ATTR_ALIAS,
|
||||
// CertificateList.FORM_ALIAS_TEXT_KEY))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
API.IMPORT_PASSWORD_ATTR_NAME,
|
||||
CertificateList.FORM_IMPORT_PASSWORD_TEXT_KEY,
|
||||
|
|
|
@ -159,7 +159,7 @@ public class CertificateList implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_CERTIFICATE_REMOVE)
|
||||
.withConfirm(() -> FORM_ACTION_MESSAGE_REMOVE_CONFIRM_TEXT_KEY)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
this::removeCertificate,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> grantCheck.iw(), false);
|
||||
|
|
|
@ -8,6 +8,10 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content.configs;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -18,12 +22,17 @@ import ch.ethz.seb.sebserver.gbl.api.API;
|
|||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.Configuration;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationValue;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.TemplateAttribute;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
|
@ -37,6 +46,9 @@ 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.seb.examconfig.DeleteExamConfiguration;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurationValues;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetConfigurations;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetTemplateAttributePage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
|
||||
|
@ -72,11 +84,23 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.configtemplate.attrs.list.view");
|
||||
private static final LocTextKey ATTRIBUTES_LIST_GROUP_TEXT_KEY =
|
||||
new LocTextKey("sebserver.configtemplate.attrs.list.group");
|
||||
private static final LocTextKey ATTRIBUTES_LIST_VALUE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.configtemplate.attrs.list.value");
|
||||
private static final LocTextKey ATTRIBUTES_LIST_TYPE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.configtemplate.attrs.list.type");
|
||||
private static final LocTextKey EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.configtemplate.attr.info.pleaseSelect");
|
||||
|
||||
static final LocTextKey CONFIRM_DELETE =
|
||||
new LocTextKey("sebserver.configtemplate.message.confirm.delete");
|
||||
static final LocTextKey DELETE_CONFIRM_TITLE =
|
||||
new LocTextKey("sebserver.dialog.confirm.title");
|
||||
|
||||
private final static LocTextKey DELETE_ERROR_DEPENDENCY =
|
||||
new LocTextKey("sebserver.configtemplate.message.delete.partialerror");
|
||||
private final static LocTextKey DELETE_CONFIRM =
|
||||
new LocTextKey("sebserver.configtemplate.message.delete.confirm");
|
||||
|
||||
private final PageService pageService;
|
||||
private final RestService restService;
|
||||
private final CurrentUser currentUser;
|
||||
|
@ -192,6 +216,20 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
TemplateAttribute.FILTER_ATTR_TYPE,
|
||||
this.resourceService::getAttributeTypeResources);
|
||||
|
||||
// TODO move this to an supplier that also can be updated
|
||||
// the follow-up configuration
|
||||
final Configuration configuration = this.restService
|
||||
.getBuilder(GetConfigurations.class)
|
||||
.withQueryParam(Configuration.FILTER_ATTR_CONFIGURATION_NODE_ID, examConfig.getModelId())
|
||||
.withQueryParam(Configuration.FILTER_ATTR_FOLLOWUP, Constants.TRUE_STRING)
|
||||
.call()
|
||||
.map(Utils::toSingleton)
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.CONFIGURATION, error))
|
||||
.getOrThrow();
|
||||
final AttributeValueSupplier attributeValueSupplier = new AttributeValueSupplier(
|
||||
this.pageService,
|
||||
configuration.getModelId());
|
||||
|
||||
final EntityTable<TemplateAttribute> attrTable =
|
||||
this.pageService.entityTableBuilder(
|
||||
Domain.CONFIGURATION_NODE.TYPE_NAME + "_Template",
|
||||
|
@ -200,6 +238,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
API.PARAM_PARENT_MODEL_ID,
|
||||
entityKey.modelId))
|
||||
.withPaging(15)
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CONFIGURATION_ATTRIBUTE.ATTR_NAME,
|
||||
ATTRIBUTES_LIST_NAME_TEXT_KEY,
|
||||
|
@ -207,6 +246,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.withFilter(this.nameFilter)
|
||||
.sortable()
|
||||
.widthProportion(3))
|
||||
|
||||
.withColumn(new ColumnDefinition<TemplateAttribute>(
|
||||
Domain.CONFIGURATION_ATTRIBUTE.ATTR_TYPE,
|
||||
ATTRIBUTES_LIST_TYPE_TEXT_KEY,
|
||||
|
@ -214,6 +254,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.withFilter(typeFilter)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.ORIENTATION.ATTR_VIEW_ID,
|
||||
ATTRIBUTES_LIST_VIEW_TEXT_KEY,
|
||||
|
@ -221,6 +262,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.withFilter(viewFilter)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.ORIENTATION.ATTR_GROUP_ID,
|
||||
ATTRIBUTES_LIST_GROUP_TEXT_KEY,
|
||||
|
@ -228,6 +270,13 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.withFilter(this.groupFilter)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<TemplateAttribute>(
|
||||
Domain.CONFIGURATION_VALUE.ATTR_VALUE,
|
||||
ATTRIBUTES_LIST_VALUE_TEXT_KEY,
|
||||
attr -> attributeValueSupplier.getAttributeValue(attr.getConfigAttribute().id))
|
||||
.widthProportion(1))
|
||||
|
||||
.withDefaultActionIf(
|
||||
() -> modifyGrant,
|
||||
() -> pageActionBuilder
|
||||
|
@ -249,7 +298,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_EDIT)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
attrTable::getSelection,
|
||||
attrTable::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant, false)
|
||||
|
@ -257,8 +306,8 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_SET_DEFAULT)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
attrTable::getSelection,
|
||||
action -> this.resetToDefaults(action, attrTable),
|
||||
attrTable::getMultiSelection,
|
||||
action -> this.resetToDefaults(attributeValueSupplier, action, attrTable),
|
||||
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> modifyGrant, false)
|
||||
|
@ -266,7 +315,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_REMOVE_VIEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
attrTable::getSelection,
|
||||
attrTable::getMultiSelection,
|
||||
action -> this.removeFormView(action, attrTable),
|
||||
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
|
@ -275,7 +324,7 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_ATTR_LIST_ATTACH_DEFAULT_VIEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
attrTable::getSelection,
|
||||
attrTable::getMultiSelection,
|
||||
action -> this.attachView(action, attrTable),
|
||||
EMPTY_ATTRIBUTE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
|
@ -291,6 +340,12 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_DELETE)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_DELETE)
|
||||
.withExec(this::deleteConfiguration)
|
||||
.publishIf(() -> writeGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_CREATE_CONFIG)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.sebxamConfigCreationPopup.configCreationFunction(
|
||||
|
@ -316,6 +371,36 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
|
||||
}
|
||||
|
||||
private PageAction deleteConfiguration(final PageAction action) {
|
||||
final ConfigurationNode configNode = this.restService
|
||||
.getBuilder(GetExamConfigNode.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final Result<EntityProcessingReport> call = this.restService
|
||||
.getBuilder(DeleteExamConfiguration.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
||||
.call();
|
||||
|
||||
final PageContext pageContext = action.pageContext();
|
||||
|
||||
final EntityProcessingReport report = call.getOrThrow();
|
||||
final String configName = configNode.toName().name;
|
||||
if (report.getErrors().isEmpty()) {
|
||||
pageContext.publishPageMessage(DELETE_CONFIRM_TITLE, new LocTextKey(DELETE_CONFIRM.name, configName));
|
||||
} else {
|
||||
pageContext.publishPageMessage(
|
||||
DELETE_CONFIRM_TITLE,
|
||||
new LocTextKey(DELETE_ERROR_DEPENDENCY.name, configName,
|
||||
report.getErrors().iterator().next().getErrorMessage().systemMessage));
|
||||
}
|
||||
|
||||
return this.pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_LIST)
|
||||
.create();
|
||||
}
|
||||
|
||||
private String getAttributeName(final TemplateAttribute attribute) {
|
||||
|
||||
final String name = this.i18nSupport.getText(
|
||||
|
@ -329,12 +414,14 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
}
|
||||
|
||||
private PageAction resetToDefaults(
|
||||
final AttributeValueSupplier attributeValueSupplier,
|
||||
final PageAction action,
|
||||
final EntityTable<TemplateAttribute> attrTable) {
|
||||
|
||||
final PageAction resetToDefaults = this.examConfigurationService.resetToDefaults(action);
|
||||
// reload the list
|
||||
attrTable.applyFilter();
|
||||
attributeValueSupplier.update();
|
||||
attrTable.updateCurrentPage();
|
||||
return resetToDefaults;
|
||||
}
|
||||
|
||||
|
@ -343,8 +430,8 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
final EntityTable<TemplateAttribute> attrTable) {
|
||||
|
||||
final PageAction removeFormView = this.examConfigurationService.removeFromView(action);
|
||||
// reload the list
|
||||
attrTable.applyFilter();
|
||||
// reload the page
|
||||
attrTable.updateCurrentPage();
|
||||
return removeFormView;
|
||||
}
|
||||
|
||||
|
@ -353,9 +440,54 @@ public class ConfigTemplateForm implements TemplateComposer {
|
|||
final EntityTable<TemplateAttribute> attrTable) {
|
||||
|
||||
final PageAction attachView = this.examConfigurationService.attachToDefaultView(action);
|
||||
// reload the list
|
||||
attrTable.applyFilter();
|
||||
// reload the page
|
||||
attrTable.updateCurrentPage();
|
||||
return attachView;
|
||||
}
|
||||
|
||||
private final class AttributeValueSupplier {
|
||||
|
||||
private final PageService pageService;
|
||||
private final String configurationId;
|
||||
private final Map<Long, String> attrValueMapping = new HashMap<>();
|
||||
|
||||
public AttributeValueSupplier(
|
||||
final PageService pageService,
|
||||
final String configurationId) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.configurationId = configurationId;
|
||||
update();
|
||||
}
|
||||
|
||||
public String getAttributeValue(final Long attributeId) {
|
||||
if (this.attrValueMapping.containsKey(attributeId)) {
|
||||
return this.attrValueMapping.get(attributeId);
|
||||
} else {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
}
|
||||
|
||||
private void update() {
|
||||
this.attrValueMapping.clear();
|
||||
|
||||
this.pageService.getRestService()
|
||||
.getBuilder(GetConfigurationValues.class)
|
||||
.withQueryParam(
|
||||
ConfigurationValue.FILTER_ATTR_CONFIGURATION_ID,
|
||||
this.configurationId)
|
||||
.call()
|
||||
.getOrElse(Collections::emptyList)
|
||||
.stream()
|
||||
.forEach(val -> {
|
||||
if (this.attrValueMapping.containsKey(val.attributeId)) {
|
||||
this.attrValueMapping.put(val.attributeId,
|
||||
this.attrValueMapping.get(val.attributeId) + "," + val.value);
|
||||
} else {
|
||||
this.attrValueMapping.put(val.attributeId, val.value);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -147,7 +147,7 @@ public class ConfigTemplateList implements TemplateComposer {
|
|||
.publishIf(examConfigGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW_FROM_LIST)
|
||||
.withSelect(templateTable::getSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
.withSelect(templateTable::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_TEMPLATE_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
package ch.ethz.seb.sebserver.gui.content.configs;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -27,6 +28,7 @@ import org.springframework.stereotype.Component;
|
|||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.client.ClientCredentials;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.SEBClientConfig;
|
||||
|
@ -44,17 +46,22 @@ import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
|||
import ch.ethz.seb.sebserver.gui.service.page.PageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
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.download.DownloadService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.download.SEBClientConfigDownload;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.ActivateClientConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.DeactivateClientConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.DeleteClientConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.GetClientCredentials;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.NewClientConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.clientconfig.SaveClientConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
|
@ -69,6 +76,19 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.clientconfig.form.title");
|
||||
private static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.name");
|
||||
private static final LocTextKey FORM_UPDATE_USER_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.update.user");
|
||||
private static final LocTextKey FORM_UPDATE_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.update.time");
|
||||
|
||||
private static final LocTextKey CLIENT_CREDENTIALS_TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.credentials.title");
|
||||
private static final LocTextKey CLIENT_CREDENTIALS_INFO_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.credentials.info");
|
||||
private static final LocTextKey CLIENT_CREDENTIALS_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.credentials.name");
|
||||
private static final LocTextKey CLIENT_CREDENTIALS_SECRET_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.credentials.secret");
|
||||
|
||||
private static final LocTextKey FORM_DATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.date");
|
||||
|
@ -114,6 +134,11 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
private static final LocTextKey FORM_CONFIRM_ENCRYPT_SECRET_TEXT_KEY =
|
||||
new LocTextKey("sebserver.clientconfig.form.encryptSecret.confirm");
|
||||
|
||||
private static final LocTextKey DELETE_CONFIRM =
|
||||
new LocTextKey("sebserver.clientconfig.action.delete.confirm");
|
||||
private static final LocTextKey DELETE_SUCCESS =
|
||||
new LocTextKey("sebserver.clientconfig.action.delete.success");
|
||||
|
||||
private static final String DEFAULT_PING_INTERVAL = String.valueOf(1000);
|
||||
private static final String FALLBACK_DEFAULT_TIME = String.valueOf(30 * Constants.SECOND_IN_MILLIS);
|
||||
private static final String FALLBACK_DEFAULT_ATTEMPTS = String.valueOf(5);
|
||||
|
@ -193,6 +218,18 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_DELETE)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> DELETE_CONFIRM)
|
||||
.withExec(this::deleteConnectionConfig)
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_SHOW_CREDENTIALS)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(getClientCredentialFunction(this.pageService, this.cryptor))
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.publishIf(() -> modifyGrant && isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_EXPORT)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> {
|
||||
|
@ -234,6 +271,24 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
.publishIf(() -> !isReadonly);
|
||||
}
|
||||
|
||||
private PageAction deleteConnectionConfig(final PageAction pageAction) {
|
||||
|
||||
this.restService.getBuilder(DeleteClientConfig.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, pageAction.getEntityKey().modelId)
|
||||
.call()
|
||||
.onError(error -> pageAction.pageContext().notifyUnexpectedError(error))
|
||||
.ifPresent(rep -> {
|
||||
if (rep.getErrors().isEmpty()) {
|
||||
pageAction.pageContext().publishInfo(DELETE_SUCCESS);
|
||||
} else {
|
||||
pageAction.pageContext().notifyUnexpectedError(
|
||||
new RuntimeException(rep.errors.iterator().next().errorMessage.details));
|
||||
}
|
||||
});
|
||||
|
||||
return pageAction;
|
||||
}
|
||||
|
||||
private void buildForm(
|
||||
final SEBClientConfig clientConfig,
|
||||
final PageContext formContext,
|
||||
|
@ -264,11 +319,31 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_INSTITUTION_ID,
|
||||
String.valueOf(clientConfig.getInstitutionId()))
|
||||
|
||||
.addFieldIf(() -> !isNew,
|
||||
.addFieldIf(() -> isReadonly,
|
||||
() -> FormBuilder.text(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_DATE,
|
||||
FORM_DATE_TEXT_KEY,
|
||||
i18nSupport.formatDisplayDateWithTimeZone(clientConfig.date))
|
||||
.readonly(true)
|
||||
.withInputSpan(2)
|
||||
.withEmptyCellSeparation(false))
|
||||
|
||||
.addFieldIf(() -> isReadonly,
|
||||
() -> FormBuilder.text(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_TIME,
|
||||
FORM_UPDATE_TIME_TEXT_KEY,
|
||||
i18nSupport.formatDisplayDateWithTimeZone(clientConfig.lastUpdateTime))
|
||||
.readonly(true)
|
||||
.withLabelSpan(1)
|
||||
.withInputSpan(2)
|
||||
.withEmptyCellSeparation(false))
|
||||
|
||||
.addFieldIf(() -> isReadonly,
|
||||
() -> FormBuilder.singleSelection(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER,
|
||||
FORM_UPDATE_USER_TEXT_KEY,
|
||||
clientConfig.lastUpdateUser,
|
||||
() -> this.pageService.getResourceService().userResources())
|
||||
.readonly(true))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
|
@ -503,6 +578,60 @@ public class SEBClientConfigForm implements TemplateComposer {
|
|||
}
|
||||
}
|
||||
|
||||
public static Function<PageAction, PageAction> getClientCredentialFunction(
|
||||
final PageService pageService,
|
||||
final Cryptor cryptor) {
|
||||
|
||||
final RestService restService = pageService.getResourceService().getRestService();
|
||||
return action -> {
|
||||
|
||||
final ClientCredentials credentials = restService
|
||||
.getBuilder(GetClientCredentials.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final WidgetFactory widgetFactory = pageService.getWidgetFactory();
|
||||
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
widgetFactory);
|
||||
|
||||
dialog.setDialogWidth(720);
|
||||
|
||||
dialog.open(
|
||||
CLIENT_CREDENTIALS_TITLE_TEXT_KEY,
|
||||
action.pageContext(),
|
||||
pc -> {
|
||||
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pc.getParent());
|
||||
|
||||
widgetFactory.labelLocalized(
|
||||
content,
|
||||
CustomVariant.TEXT_H3,
|
||||
CLIENT_CREDENTIALS_INFO_TEXT_KEY);
|
||||
|
||||
pageService.formBuilder(
|
||||
action.pageContext().copyOf(content))
|
||||
.readonly(true)
|
||||
.withDefaultSpanLabel(1)
|
||||
.withDefaultSpanInput(6)
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
"ClientId",
|
||||
CLIENT_CREDENTIALS_NAME_TEXT_KEY,
|
||||
credentials.clientIdAsString()))
|
||||
|
||||
.addField(FormBuilder.password(
|
||||
"ClientSecret",
|
||||
CLIENT_CREDENTIALS_SECRET_TEXT_KEY,
|
||||
cryptor.decrypt(credentials.secret).getOrThrow()))
|
||||
.build();
|
||||
});
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private static final class FormHandleAnchor {
|
||||
|
||||
FormHandle<SEBClientConfig> formHandle;
|
||||
|
|
|
@ -173,7 +173,8 @@ public class SEBClientConfigList implements TemplateComposer {
|
|||
.publishIf(clientConfigGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_MODIFY_FROM_LIST)
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.configs;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.AbstractBatchActionWizard;
|
||||
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.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SEBExamConfigBatchResetToTemplatePopup extends AbstractBatchActionWizard {
|
||||
|
||||
private final static LocTextKey FORM_TITLE =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.reset.title");
|
||||
private final static LocTextKey ACTION_DO_RESET =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.action.reset");
|
||||
private final static LocTextKey FORM_INFO =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.action.reset.info");
|
||||
|
||||
protected SEBExamConfigBatchResetToTemplatePopup(
|
||||
final PageService pageService,
|
||||
final ServerPushService serverPushService) {
|
||||
|
||||
super(pageService, serverPushService);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getTitle() {
|
||||
return FORM_TITLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getBatchActionInfo() {
|
||||
return FORM_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getBatchActionTitle() {
|
||||
return ACTION_DO_RESET;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BatchActionType getBatchActionType() {
|
||||
return BatchActionType.EXAM_CONFIG_REST_TEMPLATE_SETTINGS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Supplier<PageContext> createResultPageSupplier(
|
||||
final PageContext pageContext,
|
||||
final FormHandle<ConfigurationNode> formHandle) {
|
||||
|
||||
// No specific fields for this action
|
||||
return () -> pageContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendBatchActionRequest(
|
||||
final PageContext pageContext,
|
||||
final RestCall<BatchAction>.RestCallBuilder batchActionRequestBuilder) {
|
||||
|
||||
// Nothing to do here
|
||||
}
|
||||
|
||||
@Override
|
||||
protected FormBuilder buildSpecificFormFields(
|
||||
final PageContext formContext,
|
||||
final FormBuilder formHead,
|
||||
final boolean readonly) {
|
||||
|
||||
// No specific fields for this action
|
||||
return formHead;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.configs;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BatchActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.BatchAction;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigurationNode.ConfigurationStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormHandle;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.AbstractBatchActionWizard;
|
||||
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.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SEBExamConfigBatchStateChangePopup extends AbstractBatchActionWizard {
|
||||
|
||||
private static final String ATTR_SELECTED_TARGET_STATE = "selectedTargetState";
|
||||
|
||||
private final static LocTextKey FORM_TITLE =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.statechange.title");
|
||||
private final static LocTextKey ACTION_DO_STATE_CHANGE =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.action.statechange");
|
||||
private final static LocTextKey FORM_INFO =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.action.statechange.info");
|
||||
private final static LocTextKey FORM_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.list.batch.action.status");
|
||||
|
||||
protected SEBExamConfigBatchStateChangePopup(
|
||||
final PageService pageService,
|
||||
final ServerPushService serverPushService) {
|
||||
|
||||
super(pageService, serverPushService);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getTitle() {
|
||||
return FORM_TITLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getBatchActionInfo() {
|
||||
return FORM_INFO;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LocTextKey getBatchActionTitle() {
|
||||
return ACTION_DO_STATE_CHANGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected BatchActionType getBatchActionType() {
|
||||
return BatchActionType.EXAM_CONFIG_STATE_CHANGE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public FormBuilder buildSpecificFormFields(
|
||||
final PageContext formContext,
|
||||
final FormBuilder formHead,
|
||||
final boolean readonly) {
|
||||
|
||||
final String targetStateName = readonly
|
||||
? formContext.getAttribute(ATTR_SELECTED_TARGET_STATE)
|
||||
: ConfigurationStatus.CONSTRUCTION.name();
|
||||
|
||||
return formHead.addField(FormBuilder.singleSelection(
|
||||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
FORM_STATUS_TEXT_KEY,
|
||||
targetStateName,
|
||||
() -> this.pageService.getResourceService()
|
||||
.examConfigStatusResourcesAll())
|
||||
.readonly(readonly));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Supplier<PageContext> createResultPageSupplier(
|
||||
final PageContext pageContext,
|
||||
final FormHandle<ConfigurationNode> formHandle) {
|
||||
|
||||
return () -> pageContext.withAttribute(
|
||||
ATTR_SELECTED_TARGET_STATE,
|
||||
formHandle.getForm().getFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void extendBatchActionRequest(
|
||||
final PageContext pageContext,
|
||||
final RestCall<BatchAction>.RestCallBuilder batchActionRequestBuilder) {
|
||||
|
||||
final String targetStateName = pageContext.getAttribute(ATTR_SELECTED_TARGET_STATE);
|
||||
if (StringUtils.isBlank(targetStateName)) {
|
||||
throw new IllegalArgumentException("missing " + ATTR_SELECTED_TARGET_STATE + " from pageContext");
|
||||
}
|
||||
|
||||
batchActionRequestBuilder.withFormParam(BatchAction.ACTION_ATTRIBUT_TARGET_STATE, targetStateName);
|
||||
}
|
||||
|
||||
}
|
|
@ -71,13 +71,11 @@ public class SEBExamConfigCreationPopup {
|
|||
.setLargeDialogWidth();
|
||||
|
||||
final CreationFormContext formContext = new CreationFormContext(
|
||||
this.pageService,
|
||||
pageContext,
|
||||
copyAsTemplate,
|
||||
createFromTemplate);
|
||||
|
||||
final Predicate<FormHandle<ConfigCreationInfo>> doCopy = formHandle -> doCreate(
|
||||
this.pageService,
|
||||
pageContext,
|
||||
copyAsTemplate,
|
||||
createFromTemplate,
|
||||
|
@ -100,7 +98,6 @@ public class SEBExamConfigCreationPopup {
|
|||
}
|
||||
|
||||
private boolean doCreate(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final boolean copyAsTemplate,
|
||||
final boolean createFromTemplate,
|
||||
|
@ -111,7 +108,7 @@ public class SEBExamConfigCreationPopup {
|
|||
? NewExamConfig.class
|
||||
: CopyConfiguration.class;
|
||||
|
||||
final ConfigurationNode newConfig = pageService
|
||||
final ConfigurationNode newConfig = this.pageService
|
||||
.getRestService()
|
||||
.getBuilder(restCall)
|
||||
.withFormBinding(formHandle.getFormBinding())
|
||||
|
@ -125,34 +122,31 @@ public class SEBExamConfigCreationPopup {
|
|||
|
||||
// view either new template or configuration
|
||||
final PageAction viewCopy = (copyAsTemplate)
|
||||
? pageService.pageActionBuilder(pageContext)
|
||||
? this.pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_TEMPLATE_VIEW)
|
||||
.withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
|
||||
.create()
|
||||
: pageService.pageActionBuilder(pageContext)
|
||||
: this.pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP)
|
||||
.withEntityKey(new EntityKey(newConfig.id, EntityType.CONFIGURATION_NODE))
|
||||
.create();
|
||||
|
||||
pageService.executePageAction(viewCopy);
|
||||
this.pageService.executePageAction(viewCopy);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private final class CreationFormContext implements ModalInputDialogComposer<FormHandle<ConfigCreationInfo>> {
|
||||
|
||||
private final PageService pageService;
|
||||
private final PageContext pageContext;
|
||||
private final boolean copyAsTemplate;
|
||||
private final boolean createFromTemplate;
|
||||
|
||||
protected CreationFormContext(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext,
|
||||
final boolean copyAsTemplate,
|
||||
final boolean createFromTemplate) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.pageContext = pageContext;
|
||||
this.copyAsTemplate = copyAsTemplate;
|
||||
this.createFromTemplate = createFromTemplate;
|
||||
|
@ -161,11 +155,11 @@ public class SEBExamConfigCreationPopup {
|
|||
@Override
|
||||
public Supplier<FormHandle<ConfigCreationInfo>> compose(final Composite parent) {
|
||||
|
||||
final Composite grid = this.pageService.getWidgetFactory()
|
||||
final Composite grid = SEBExamConfigCreationPopup.this.pageService.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final EntityKey entityKey = this.pageContext.getEntityKey();
|
||||
final FormHandle<ConfigCreationInfo> formHandle = this.pageService.formBuilder(
|
||||
final FormHandle<ConfigCreationInfo> formHandle = SEBExamConfigCreationPopup.this.pageService.formBuilder(
|
||||
this.pageContext.copyOf(grid))
|
||||
.readonly(false)
|
||||
.putStaticValueIf(
|
||||
|
|
|
@ -27,6 +27,7 @@ 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.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.sebconfig.ConfigKey;
|
||||
|
@ -58,6 +59,7 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.De
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ExportConfigKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.GetExamConfigNode;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.NewExamConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.ResetToTemplateSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.seb.examconfig.SaveExamConfig;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
|
||||
|
@ -71,12 +73,19 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
|||
@GuiProfile
|
||||
public class SEBExamConfigForm implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey MESSAGE_RESET_TO_TEMPL_SUCCESS =
|
||||
new LocTextKey("sebserver.examconfig.action.restore.template.settings.success");
|
||||
static final LocTextKey FORM_TITLE_NEW =
|
||||
new LocTextKey("sebserver.examconfig.form.title.new");
|
||||
static final LocTextKey FORM_TITLE =
|
||||
new LocTextKey("sebserver.examconfig.form.title");
|
||||
static final LocTextKey FORM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.form.name");
|
||||
static final LocTextKey FORM_UPDATE_TIME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.form.update.time");
|
||||
static final LocTextKey FORM_UPDATE_USER_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.form.update.user");
|
||||
|
||||
static final LocTextKey FORM_DESCRIPTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.form.description");
|
||||
static final LocTextKey FORM_HISTORY_TEXT_KEY =
|
||||
|
@ -101,6 +110,8 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.examconfig.form.attached-to");
|
||||
static final LocTextKey FORM_ATTACHED_EXAMS_TITLE_TOOLTIP_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.form.attached-to" + Constants.TOOLTIP_TEXT_KEY_SUFFIX);
|
||||
static final LocTextKey FORM_RESET_CONFIRM =
|
||||
new LocTextKey("sebserver.examconfig.action.restore.template.settings.confirm");
|
||||
|
||||
static final LocTextKey SAVE_CONFIRM_STATE_CHANGE_WHILE_ATTACHED =
|
||||
new LocTextKey("sebserver.examconfig.action.state-change.confirm");
|
||||
|
@ -168,6 +179,16 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
.call()
|
||||
.map(names -> names != null && !names.isEmpty())
|
||||
.getOr(Boolean.FALSE);
|
||||
final boolean hasRunningExam = isAttachedToExam && this.restService
|
||||
.getBuilder(GetExamConfigMappingsPage.class)
|
||||
.withQueryParam(ExamConfigurationMap.FILTER_ATTR_CONFIG_ID, examConfig.getModelId())
|
||||
.call()
|
||||
.map(res -> res.content
|
||||
.stream()
|
||||
.filter(map -> map.examStatus == ExamStatus.RUNNING)
|
||||
.findAny()
|
||||
.isPresent())
|
||||
.getOr(false);
|
||||
|
||||
// new PageContext with actual EntityKey
|
||||
final PageContext formContext = pageContext.withEntityKey(examConfig.getEntityKey());
|
||||
|
@ -203,6 +224,28 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
: String.valueOf(examConfig.templateId),
|
||||
resourceService::getExamConfigTemplateResources)
|
||||
.readonly(!isNew))
|
||||
|
||||
.addFieldIf(() -> isReadonly,
|
||||
() -> FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_LAST_UPDATE_TIME,
|
||||
FORM_UPDATE_TIME_TEXT_KEY,
|
||||
this.pageService.getI18nSupport()
|
||||
.formatDisplayDateWithTimeZone(examConfig.lastUpdateTime))
|
||||
.readonly(true)
|
||||
.withInputSpan(2)
|
||||
.withEmptyCellSeparation(false))
|
||||
|
||||
.addFieldIf(() -> isReadonly,
|
||||
() -> FormBuilder.singleSelection(
|
||||
Domain.SEB_CLIENT_CONFIGURATION.ATTR_LAST_UPDATE_USER,
|
||||
FORM_UPDATE_USER_TEXT_KEY,
|
||||
examConfig.lastUpdateUser,
|
||||
() -> this.pageService.getResourceService().userResources())
|
||||
.readonly(true)
|
||||
.withLabelSpan(1)
|
||||
.withInputSpan(2)
|
||||
.withEmptyCellSeparation(false))
|
||||
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CONFIGURATION_NODE.ATTR_NAME,
|
||||
FORM_NAME_TEXT_KEY,
|
||||
|
@ -218,7 +261,7 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
Domain.CONFIGURATION_NODE.ATTR_STATUS,
|
||||
FORM_STATUS_TEXT_KEY,
|
||||
examConfig.status.name(),
|
||||
() -> resourceService.examConfigStatusResources(isAttachedToExam))
|
||||
() -> resourceService.examConfigStatusResources(isAttachedToExam, hasRunningExam))
|
||||
.withEmptyCellSeparation(!isReadonly))
|
||||
.buildFor((isNew)
|
||||
? this.restService.getRestCall(NewExamConfig.class)
|
||||
|
@ -248,6 +291,17 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
.withAttribute(PageContext.AttributeKeys.READ_ONLY, String.valueOf(!modifyGrant))
|
||||
.publishIf(() -> isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_RESET_TO_TEMPLATE_SETTINGS)
|
||||
.withConfirm(() -> FORM_RESET_CONFIRM)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this::restoreToTemplateSettings)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> modifyGrant
|
||||
&& isReadonly
|
||||
&& examConfig.status != ConfigurationStatus.IN_USE
|
||||
&& examConfig.templateId != null
|
||||
&& examConfig.templateId.longValue() > 0)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.sebExamConfigCreationPopup.configCreationFunction(
|
||||
|
@ -281,7 +335,7 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.withExec(formHandle::processFormSave)
|
||||
.ignoreMoveAwayFromEdit()
|
||||
.withConfirm(() -> stateChangeConfirm(isAttachedToExam, formHandle))
|
||||
.withConfirm(() -> stateChangeConfirm(hasRunningExam, formHandle))
|
||||
.publishIf(() -> !isReadonly)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_PROP_CANCEL_MODIFY)
|
||||
|
@ -341,6 +395,18 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
}
|
||||
}
|
||||
|
||||
private PageAction restoreToTemplateSettings(final PageAction action) {
|
||||
this.restService.getBuilder(ResetToTemplateSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
action.pageContext().publishInfo(MESSAGE_RESET_TO_TEMPL_SUCCESS);
|
||||
|
||||
return action;
|
||||
|
||||
}
|
||||
|
||||
private PageAction deleteConfiguration(final PageAction action) {
|
||||
final ConfigurationNode configNode = this.restService
|
||||
.getBuilder(GetExamConfigNode.class)
|
||||
|
@ -408,17 +474,17 @@ public class SEBExamConfigForm implements TemplateComposer {
|
|||
}
|
||||
|
||||
private LocTextKey stateChangeConfirm(
|
||||
final boolean isAttachedToExam,
|
||||
final boolean hasRunningExam,
|
||||
final FormHandle<ConfigurationNode> formHandle) {
|
||||
|
||||
if (isAttachedToExam) {
|
||||
if (hasRunningExam) {
|
||||
final String fieldValue = formHandle
|
||||
.getForm()
|
||||
.getFieldValue(Domain.CONFIGURATION_NODE.ATTR_STATUS);
|
||||
|
||||
if (fieldValue != null) {
|
||||
final ConfigurationStatus state = ConfigurationStatus.valueOf(fieldValue);
|
||||
if (state != ConfigurationStatus.IN_USE) {
|
||||
if (state != ConfigurationStatus.IN_USE && state != ConfigurationStatus.ARCHIVED) {
|
||||
return SAVE_CONFIRM_STATE_CHANGE_WHILE_ATTACHED;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -57,6 +57,8 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
new LocTextKey("sebserver.examconfig.list.column.description");
|
||||
private static final LocTextKey STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.list.column.status");
|
||||
private static final LocTextKey TEMPLATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.list.column.template");
|
||||
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.examconfig.info.pleaseSelect");
|
||||
|
||||
|
@ -66,10 +68,13 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
private final TableFilterAttribute descFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, ConfigurationNode.FILTER_ATTR_DESCRIPTION);
|
||||
private final TableFilterAttribute statusFilter;
|
||||
private final TableFilterAttribute templateFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final SEBExamConfigImportPopup sebExamConfigImportPopup;
|
||||
private final SEBExamConfigCreationPopup sebExamConfigCreationPopup;
|
||||
private final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup;
|
||||
private final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup;
|
||||
private final CurrentUser currentUser;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
@ -78,11 +83,15 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
final PageService pageService,
|
||||
final SEBExamConfigImportPopup sebExamConfigImportPopup,
|
||||
final SEBExamConfigCreationPopup sebExamConfigCreationPopup,
|
||||
final SEBExamConfigBatchStateChangePopup sebExamConfigBatchStateChangePopup,
|
||||
final SEBExamConfigBatchResetToTemplatePopup sebExamConfigBatchResetToTemplatePopup,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.sebExamConfigImportPopup = sebExamConfigImportPopup;
|
||||
this.sebExamConfigCreationPopup = sebExamConfigCreationPopup;
|
||||
this.sebExamConfigBatchStateChangePopup = sebExamConfigBatchStateChangePopup;
|
||||
this.sebExamConfigBatchResetToTemplatePopup = sebExamConfigBatchResetToTemplatePopup;
|
||||
this.currentUser = pageService.getCurrentUser();
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.pageSize = pageSize;
|
||||
|
@ -96,6 +105,11 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
CriteriaType.SINGLE_SELECTION,
|
||||
ConfigurationNode.FILTER_ATTR_STATUS,
|
||||
this.resourceService::examConfigStatusFilterResources);
|
||||
|
||||
this.templateFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
ConfigurationNode.FILTER_ATTR_TEMPLATE_ID,
|
||||
this.resourceService::getExamConfigTemplateResourcesSelection);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -112,6 +126,7 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
// exam configuration table
|
||||
final EntityTable<ConfigurationNode> configTable =
|
||||
this.pageService.entityTableBuilder(GetExamConfigNodePage.class)
|
||||
.withMultiSelection()
|
||||
.withStaticFilter(
|
||||
Domain.CONFIGURATION_NODE.ATTR_TYPE,
|
||||
ConfigurationType.EXAM_CONFIG.name())
|
||||
|
@ -146,6 +161,13 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
this.resourceService::localizedExamConfigStatusName)
|
||||
.withFilter(this.statusFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.CONFIGURATION_NODE.ATTR_TEMPLATE_ID,
|
||||
TEMPLATE_TEXT_KEY,
|
||||
this.resourceService.examConfigTemplateNameFunction())
|
||||
.withFilter(this.templateFilter))
|
||||
|
||||
.withDefaultAction(pageActionBuilder
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST)
|
||||
.create())
|
||||
|
@ -154,7 +176,9 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
pageContext,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_MODIFY_PROP_FROM_LIST,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST))
|
||||
ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE,
|
||||
ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
|
@ -166,7 +190,7 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_VIEW_PROP_FROM_LIST)
|
||||
.withSelect(
|
||||
configTable::getSelection,
|
||||
configTable::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
@ -179,7 +203,6 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
.publishIf(() -> examConfigGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_COPY_CONFIG_FROM_LIST)
|
||||
//.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION),
|
||||
pageAction -> {
|
||||
|
@ -199,6 +222,22 @@ public class SEBExamConfigList implements TemplateComposer {
|
|||
.noEventPropagation()
|
||||
.publishIf(() -> examConfigGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_STATE_CHANGE)
|
||||
.withSelect(
|
||||
configTable::getMultiSelection,
|
||||
this.sebExamConfigBatchStateChangePopup.popupCreationFunction(pageContext),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> examConfigGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_BULK_RESET_TO_TEMPLATE)
|
||||
.withSelect(
|
||||
configTable::getMultiSelection,
|
||||
this.sebExamConfigBatchResetToTemplatePopup.popupCreationFunction(pageContext),
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> examConfigGrant.im(), false)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_IMPORT_TO_NEW_CONFIG)
|
||||
.withSelect(
|
||||
configTable.getGrantedSelection(this.currentUser, NO_MODIFY_PRIVILEGE_ON_OTHER_INSTITUTION),
|
||||
|
|
|
@ -61,12 +61,13 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.download.DownloadService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.ArchiveExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckExamConsistency;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.CheckSEBRestriction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetDefaultExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.TestLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.quiz.GetQuizData;
|
||||
|
@ -118,6 +119,8 @@ public class ExamForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.exam.form.examTemplate");
|
||||
private static final LocTextKey FORM_EXAM_TEMPLATE_ERROR =
|
||||
new LocTextKey("sebserver.exam.form.examTemplate.error");
|
||||
private static final LocTextKey EXAM_ARCHIVE_CONFIRM =
|
||||
new LocTextKey("sebserver.exam.action.archive.confirm");
|
||||
|
||||
private final static LocTextKey CONSISTENCY_MESSAGE_TITLE =
|
||||
new LocTextKey("sebserver.exam.consistency.title");
|
||||
|
@ -143,7 +146,7 @@ public class ExamForm implements TemplateComposer {
|
|||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final ExamSEBRestrictionSettings examSEBRestrictionSettings;
|
||||
private final ExamProctoringSettings examProctoringSettings;
|
||||
private final ProctoringSettingsPopup proctoringSettingsPopup;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final RestService restService;
|
||||
private final ExamDeletePopup examDeletePopup;
|
||||
|
@ -154,7 +157,7 @@ public class ExamForm implements TemplateComposer {
|
|||
protected ExamForm(
|
||||
final PageService pageService,
|
||||
final ExamSEBRestrictionSettings examSEBRestrictionSettings,
|
||||
final ExamProctoringSettings examProctoringSettings,
|
||||
final ProctoringSettingsPopup proctoringSettingsPopup,
|
||||
final ExamToConfigBindingForm examToConfigBindingForm,
|
||||
final DownloadService downloadService,
|
||||
final ExamDeletePopup examDeletePopup,
|
||||
|
@ -165,7 +168,7 @@ public class ExamForm implements TemplateComposer {
|
|||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.examSEBRestrictionSettings = examSEBRestrictionSettings;
|
||||
this.examProctoringSettings = examProctoringSettings;
|
||||
this.proctoringSettingsPopup = proctoringSettingsPopup;
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.restService = this.resourceService.getRestService();
|
||||
this.examDeletePopup = examDeletePopup;
|
||||
|
@ -240,14 +243,13 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
final BooleanSupplier isNew = () -> importFromQuizData;
|
||||
final BooleanSupplier isNotNew = () -> !isNew.getAsBoolean();
|
||||
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(exam);
|
||||
final boolean modifyGrant = userGrantCheck.m();
|
||||
final boolean writeGrant = userGrantCheck.w();
|
||||
final EntityGrantCheck entityGrantCheck = currentUser.entityGrantCheck(exam);
|
||||
final boolean modifyGrant = entityGrantCheck.m();
|
||||
final boolean writeGrant = entityGrantCheck.w();
|
||||
final ExamStatus examStatus = exam.getStatus();
|
||||
final boolean editable = modifyGrant && (examStatus == ExamStatus.UP_COMING ||
|
||||
examStatus == ExamStatus.RUNNING);
|
||||
final boolean editable = modifyGrant &&
|
||||
(examStatus == ExamStatus.UP_COMING || examStatus == ExamStatus.RUNNING);
|
||||
|
||||
// TODO this is not performat try to improve by doing one check with the CheckExamConsistency above
|
||||
final boolean sebRestrictionAvailable = testSEBRestrictionAPI(exam);
|
||||
final boolean isRestricted = readonly && sebRestrictionAvailable && this.restService
|
||||
.getBuilder(CheckSEBRestriction.class)
|
||||
|
@ -317,7 +319,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_START_URL,
|
||||
FORM_QUIZ_URL_TEXT_KEY,
|
||||
exam.startURL)
|
||||
exam.getStartURL())
|
||||
.readonly(true)
|
||||
.withInputSpan(7)
|
||||
.withEmptyCellSeparation(false))
|
||||
|
@ -325,7 +327,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_DESCRIPTION,
|
||||
FORM_DESCRIPTION_TEXT_KEY,
|
||||
exam.description)
|
||||
exam.getDescription())
|
||||
.asHTML(50)
|
||||
.readonly(true)
|
||||
.withInputSpan(6)
|
||||
|
@ -391,7 +393,7 @@ public class ExamForm implements TemplateComposer {
|
|||
}
|
||||
|
||||
final boolean proctoringEnabled = importFromQuizData ? false : this.restService
|
||||
.getBuilder(GetProctoringSettings.class)
|
||||
.getBuilder(GetExamProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.map(ProctoringServiceSettings::getEnableProctoring)
|
||||
|
@ -408,6 +410,17 @@ public class ExamForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && readonly && editable)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_DELETE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.examDeletePopup.deleteWizardFunction(pageContext))
|
||||
.publishIf(() -> writeGrant && readonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_ARCHIVE)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> EXAM_ARCHIVE_CONFIRM)
|
||||
.withExec(this::archiveExam)
|
||||
.publishIf(() -> writeGrant && readonly && examStatus == ExamStatus.FINISHED)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_SAVE)
|
||||
.withExec(action -> (importFromQuizData)
|
||||
? importExam(action, formHandle, sebRestrictionAvailable && exam.status == ExamStatus.RUNNING)
|
||||
|
@ -432,7 +445,7 @@ public class ExamForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.withExec(this.examSEBRestrictionSettings.settingsFunction(this.pageService))
|
||||
.withAttribute(ExamSEBRestrictionSettings.PAGE_CONTEXT_ATTR_LMS_ID, String.valueOf(exam.lmsSetupId))
|
||||
.withAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY, String.valueOf(!modifyGrant))
|
||||
.withAttribute(PageContext.AttributeKeys.FORCE_READ_ONLY, String.valueOf(!modifyGrant || !editable))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> sebRestrictionAvailable && readonly)
|
||||
|
||||
|
@ -451,20 +464,15 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.EXAM_PROCTORING_ON)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
|
||||
.withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> editable && proctoringEnabled && readonly)
|
||||
.publishIf(() -> proctoringEnabled && readonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_PROCTORING_OFF)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.examProctoringSettings.settingsFunction(this.pageService, modifyGrant))
|
||||
.withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, modifyGrant && editable))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> editable && !proctoringEnabled && readonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_DELETE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.examDeletePopup.deleteWizardFunction(pageContext))
|
||||
.publishIf(() -> writeGrant && readonly);
|
||||
.publishIf(() -> !proctoringEnabled && readonly);
|
||||
|
||||
// additional data in read-only view
|
||||
if (readonly && !importFromQuizData) {
|
||||
|
@ -472,7 +480,7 @@ public class ExamForm implements TemplateComposer {
|
|||
this.examFormConfigs.compose(
|
||||
formContext
|
||||
.copyOf(content)
|
||||
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
|
||||
.withAttribute(ATTR_READ_GRANT, String.valueOf(entityGrantCheck.r()))
|
||||
.withAttribute(ATTR_EDITABLE, String.valueOf(editable))
|
||||
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
|
||||
|
||||
|
@ -480,12 +488,22 @@ public class ExamForm implements TemplateComposer {
|
|||
this.examFormIndicators.compose(
|
||||
formContext
|
||||
.copyOf(content)
|
||||
.withAttribute(ATTR_READ_GRANT, String.valueOf(userGrantCheck.r()))
|
||||
.withAttribute(ATTR_READ_GRANT, String.valueOf(entityGrantCheck.r()))
|
||||
.withAttribute(ATTR_EDITABLE, String.valueOf(editable))
|
||||
.withAttribute(ATTR_EXAM_STATUS, examStatus.name()));
|
||||
}
|
||||
}
|
||||
|
||||
private PageAction archiveExam(final PageAction action) {
|
||||
|
||||
this.restService.getBuilder(ArchiveExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, action.getEntityKey().modelId)
|
||||
.call()
|
||||
.onError(error -> action.pageContext().notifyUnexpectedError(error));
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private String getDefaultExamTemplateId() {
|
||||
return this.restService.getBuilder(GetDefaultExamTemplate.class)
|
||||
.call()
|
||||
|
@ -578,7 +596,7 @@ public class ExamForm implements TemplateComposer {
|
|||
}
|
||||
|
||||
private boolean testSEBRestrictionAPI(final Exam exam) {
|
||||
if (exam.status == ExamStatus.CORRUPT_NO_LMS_CONNECTION || exam.status == ExamStatus.CORRUPT_INVALID_ID) {
|
||||
if (!exam.isLmsAvailable() || exam.status == ExamStatus.ARCHIVED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -133,7 +133,7 @@ public class ExamFormIndicators implements TemplateComposer {
|
|||
.newAction(ActionDefinition.EXAM_INDICATOR_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
indicatorTable::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> editable && indicatorTable.hasAnyContent(), false)
|
||||
|
@ -141,7 +141,7 @@ public class ExamFormIndicators implements TemplateComposer {
|
|||
.newAction(ActionDefinition.EXAM_INDICATOR_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
indicatorTable::getMultiSelection,
|
||||
this::deleteSelectedIndicator,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> editable && indicatorTable.hasAnyContent(), false)
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.function.BiConsumer;
|
|||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.TableItem;
|
||||
|
@ -29,7 +30,6 @@ import ch.ethz.seb.sebserver.gbl.model.Entity;
|
|||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam.ExamStatus;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamConfigurationMap;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
|
@ -76,6 +76,8 @@ public class ExamList implements TemplateComposer {
|
|||
new LocTextKey("sebserver.exam.list.column.lmssetup");
|
||||
public final static LocTextKey COLUMN_TITLE_NAME_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.name");
|
||||
public final static LocTextKey COLUMN_TITLE_STATE_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.state");
|
||||
public final static LocTextKey COLUMN_TITLE_TYPE_KEY =
|
||||
new LocTextKey("sebserver.exam.list.column.type");
|
||||
public final static LocTextKey NO_MODIFY_OF_OUT_DATED_EXAMS =
|
||||
|
@ -86,7 +88,8 @@ public class ExamList implements TemplateComposer {
|
|||
private final TableFilterAttribute institutionFilter;
|
||||
private final TableFilterAttribute lmsFilter;
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
new TableFilterAttribute(CriteriaType.TEXT, Domain.EXAM.ATTR_QUIZ_NAME);
|
||||
private final TableFilterAttribute stateFilter;
|
||||
private final TableFilterAttribute typeFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
|
@ -111,6 +114,11 @@ public class ExamList implements TemplateComposer {
|
|||
LmsSetup.FILTER_ATTR_LMS_SETUP,
|
||||
this.resourceService::lmsSetupResource);
|
||||
|
||||
this.stateFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Exam.FILTER_ATTR_STATUS,
|
||||
this.resourceService::localizedExamStatusSelection);
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Exam.FILTER_ATTR_TYPE,
|
||||
|
@ -168,26 +176,33 @@ public class ExamList implements TemplateComposer {
|
|||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
Domain.EXAM.ATTR_QUIZ_NAME,
|
||||
COLUMN_TITLE_NAME_KEY,
|
||||
Exam::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
Domain.EXAM.ATTR_QUIZ_START_TIME,
|
||||
new LocTextKey(
|
||||
EXAM_LIST_COLUMN_START_TIME,
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getStartTime)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE,
|
||||
QuizData.FILTER_ATTR_START_TIME,
|
||||
Domain.EXAM.ATTR_QUIZ_START_TIME,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_STATUS,
|
||||
COLUMN_TITLE_STATE_KEY,
|
||||
this.resourceService::localizedExamStatusName)
|
||||
.withFilter(this.stateFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<Exam>(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
COLUMN_TITLE_TYPE_KEY,
|
||||
|
@ -210,7 +225,8 @@ public class ExamList implements TemplateComposer {
|
|||
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM);
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_MODIFY_FROM_LIST)
|
||||
|
@ -257,7 +273,12 @@ public class ExamList implements TemplateComposer {
|
|||
final Exam exam,
|
||||
final PageService pageService) {
|
||||
|
||||
if (exam.getStatus() == ExamStatus.UP_COMING || exam.getStatus() == ExamStatus.FINISHED) {
|
||||
if (BooleanUtils.isFalse(exam.isLmsAvailable())) {
|
||||
item.setData(RWT.CUSTOM_VARIANT, CustomVariant.DISABLED.key);
|
||||
return;
|
||||
}
|
||||
|
||||
if (exam.getStatus() != ExamStatus.RUNNING) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
|||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.IndicatorTemplate;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
|
@ -39,11 +40,12 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.DeleteIndicatorTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplateProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicatorTemplatePage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.NewExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplate;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.GrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser.EntityGrantCheck;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.table.EntityTable;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
@ -93,13 +95,17 @@ public class ExamTemplateForm implements TemplateComposer {
|
|||
private final ResourceService resourceService;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final RestService restService;
|
||||
private final ProctoringSettingsPopup proctoringSettingsPopup;
|
||||
|
||||
public ExamTemplateForm(final PageService pageService) {
|
||||
public ExamTemplateForm(
|
||||
final PageService pageService,
|
||||
final ProctoringSettingsPopup proctoringSettingsPopup) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.restService = pageService.getRestService();
|
||||
this.proctoringSettingsPopup = proctoringSettingsPopup;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -185,13 +191,20 @@ public class ExamTemplateForm implements TemplateComposer {
|
|||
? this.restService.getRestCall(NewExamTemplate.class)
|
||||
: this.restService.getRestCall(SaveExamTemplate.class));
|
||||
|
||||
final GrantCheck userGrant = currentUser.grantCheck(EntityType.EXAM_TEMPLATE);
|
||||
final boolean proctoringEnabled = !isNew && this.restService
|
||||
.getBuilder(GetExamTemplateProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.map(ProctoringServiceSettings::getEnableProctoring)
|
||||
.getOr(false);
|
||||
|
||||
final EntityGrantCheck userGrantCheck = currentUser.entityGrantCheck(examTemplate);
|
||||
// propagate content actions to action-pane
|
||||
this.pageService.pageActionBuilder(formContext.clearEntityKeys())
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TEMPLATE_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> userGrant.im() && readonly)
|
||||
.publishIf(() -> userGrantCheck.m() && readonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TEMPLATE_SAVE)
|
||||
.withEntityKey(entityKey)
|
||||
|
@ -208,9 +221,19 @@ public class ExamTemplateForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> EXAM_TEMPLATE_DELETE_CONFIRM)
|
||||
.withExec(this::deleteExamTemplate)
|
||||
.publishIf(() -> userGrant.iw() && readonly)
|
||||
.publishIf(() -> userGrantCheck.w() && readonly)
|
||||
|
||||
;
|
||||
.newAction(ActionDefinition.EXAM_TEMPLATE_PROCTORING_ON)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, userGrantCheck.m()))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> proctoringEnabled && readonly)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TEMPLATE_PROCTORING_OFF)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.proctoringSettingsPopup.settingsFunction(this.pageService, userGrantCheck.m()))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> !proctoringEnabled && readonly);
|
||||
|
||||
if (readonly) {
|
||||
|
||||
|
@ -250,7 +273,7 @@ public class ExamTemplateForm implements TemplateComposer {
|
|||
.asMarkup()
|
||||
.widthProportion(4))
|
||||
.withDefaultActionIf(
|
||||
() -> userGrant.im(),
|
||||
() -> userGrantCheck.m(),
|
||||
() -> actionBuilder
|
||||
.newAction(ActionDefinition.INDICATOR_TEMPLATE_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
|
@ -268,22 +291,22 @@ public class ExamTemplateForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.INDICATOR_TEMPLATE_MODIFY_FROM_LIST)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
indicatorTable::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false)
|
||||
.publishIf(() -> userGrantCheck.m() && indicatorTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.INDICATOR_TEMPLATE_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
indicatorTable::getSelection,
|
||||
indicatorTable::getMultiSelection,
|
||||
this::deleteSelectedIndicator,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im() && indicatorTable.hasAnyContent(), false)
|
||||
.publishIf(() -> userGrantCheck.m() && indicatorTable.hasAnyContent(), false)
|
||||
|
||||
.newAction(ActionDefinition.INDICATOR_TEMPLATE_NEW)
|
||||
.withParentEntityKey(entityKey)
|
||||
.publishIf(() -> userGrant.im());
|
||||
.publishIf(() -> userGrantCheck.m());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -175,11 +175,13 @@ public class ExamTemplateList implements TemplateComposer {
|
|||
.publishIf(userGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TEMPLATE_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_TEMPLATE_MODIFY_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(table::getMultiSelection, PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> userGrant.im(), false);
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,246 @@
|
|||
/*
|
||||
* 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.exam;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API.BulkActionType;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.ErrorMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityDependency;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityProcessingReport;
|
||||
import ch.ethz.seb.sebserver.gbl.model.institution.LmsSetup;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.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.PageMessageException;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputWizard;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputWizard.WizardAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputWizard.WizardPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCallError;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.DeleteLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetup;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.lmssetup.GetLmsSetupDependencies;
|
||||
import ch.ethz.seb.sebserver.gui.table.ColumnDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class LmsSetupDeletePopup {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(LmsSetupDeletePopup.class);
|
||||
|
||||
private final static LocTextKey FORM_TITLE =
|
||||
new LocTextKey("sebserver.lmssetup.delete.form.title");
|
||||
private final static LocTextKey FORM_INFO =
|
||||
new LocTextKey("sebserver.lmssetup.delete.form.info");
|
||||
private final static LocTextKey FORM_REPORT_INFO =
|
||||
new LocTextKey("sebserver.lmssetup.delete.report.info");
|
||||
private final static LocTextKey FORM_REPORT_LIST_TYPE =
|
||||
new LocTextKey("sebserver.lmssetup.delete.report.list.type");
|
||||
private final static LocTextKey FORM_REPORT_LIST_NAME =
|
||||
new LocTextKey("sebserver.lmssetup.delete.report.list.name");
|
||||
private final static LocTextKey FORM_REPORT_LIST_DESC =
|
||||
new LocTextKey("sebserver.lmssetup.delete.report.list.description");
|
||||
private final static LocTextKey FORM_REPORT_NONE =
|
||||
new LocTextKey("sebserver.lmssetup.delete.report.list.empty");
|
||||
|
||||
private final static LocTextKey ACTION_DELETE =
|
||||
new LocTextKey("sebserver.lmssetup.delete.action.delete");
|
||||
|
||||
private final static LocTextKey DELETE_CONFIRM_TITLE =
|
||||
new LocTextKey("sebserver.lmssetup.delete.confirm.title");
|
||||
private final static LocTextKey DELETE_ERROR_CONSISTENCY =
|
||||
new LocTextKey("sebserver.lmssetup.action.delete.consistency.error");
|
||||
|
||||
private final PageService pageService;
|
||||
|
||||
protected LmsSetupDeletePopup(final PageService pageService) {
|
||||
this.pageService = pageService;
|
||||
}
|
||||
|
||||
public Function<PageAction, PageAction> deleteWizardFunction(final PageContext pageContext) {
|
||||
return action -> {
|
||||
|
||||
final ModalInputWizard<PageContext> wizard =
|
||||
new ModalInputWizard<PageContext>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.pageService.getWidgetFactory())
|
||||
.setVeryLargeDialogWidth();
|
||||
|
||||
final String page1Id = "DELETE_PAGE";
|
||||
final Predicate<PageContext> callback = pc -> doDelete(this.pageService, pc);
|
||||
final BiFunction<PageContext, Composite, Supplier<PageContext>> composePage1 =
|
||||
(prefPageContext, content) -> composeDeleteDialog(content,
|
||||
(prefPageContext != null) ? prefPageContext : pageContext);
|
||||
|
||||
final WizardPage<PageContext> page1 = new WizardPage<>(
|
||||
page1Id,
|
||||
true,
|
||||
composePage1,
|
||||
new WizardAction<>(ACTION_DELETE, callback));
|
||||
|
||||
wizard.open(FORM_TITLE, Utils.EMPTY_EXECUTION, page1);
|
||||
|
||||
return action;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean doDelete(
|
||||
final PageService pageService,
|
||||
final PageContext pageContext) {
|
||||
|
||||
try {
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final LmsSetup toDelete = this.pageService
|
||||
.getRestService()
|
||||
.getBuilder(GetLmsSetup.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final RestCall<EntityProcessingReport>.RestCallBuilder restCallBuilder = this.pageService.getRestService()
|
||||
.getBuilder(DeleteLmsSetup.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name());
|
||||
|
||||
final Result<EntityProcessingReport> deleteCall = restCallBuilder.call();
|
||||
if (deleteCall.hasError()) {
|
||||
final Exception error = deleteCall.getError();
|
||||
if (error instanceof RestCallError) {
|
||||
final APIMessage message = ((RestCallError) error)
|
||||
.getAPIMessages()
|
||||
.stream()
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
if (message != null && ErrorMessage.INTEGRITY_VALIDATION.isOf(message)) {
|
||||
pageContext.publishPageMessage(new PageMessageException(DELETE_ERROR_CONSISTENCY));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
final EntityProcessingReport report = deleteCall.getOrThrow();
|
||||
|
||||
final PageAction action = this.pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.LMS_SETUP_VIEW_LIST)
|
||||
.create();
|
||||
|
||||
this.pageService.firePageEvent(
|
||||
new ActionEvent(action),
|
||||
action.pageContext());
|
||||
|
||||
final List<EntityKey> dependencies = report.results.stream()
|
||||
.filter(key -> !key.equals(entityKey))
|
||||
.collect(Collectors.toList());
|
||||
pageContext.publishPageMessage(
|
||||
DELETE_CONFIRM_TITLE,
|
||||
new LocTextKey(
|
||||
"sebserver.lmssetup.delete.confirm.message",
|
||||
toDelete.toName().name,
|
||||
dependencies.size(),
|
||||
(report.errors.isEmpty()) ? "no" : String.valueOf((report.errors.size()))));
|
||||
return true;
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while trying to delete LMS Setup:", e);
|
||||
pageContext.notifyUnexpectedError(e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<PageContext> composeDeleteDialog(
|
||||
final Composite parent,
|
||||
final PageContext pageContext) {
|
||||
|
||||
final I18nSupport i18nSupport = this.pageService.getI18nSupport();
|
||||
final Composite grid = this.pageService.getWidgetFactory()
|
||||
.createPopupScrollComposite(parent);
|
||||
|
||||
final Label title = this.pageService.getWidgetFactory()
|
||||
.labelLocalized(grid, CustomVariant.TEXT_H3, FORM_INFO);
|
||||
final GridData gridData = new GridData();
|
||||
gridData.horizontalIndent = 10;
|
||||
gridData.verticalIndent = 10;
|
||||
title.setLayoutData(gridData);
|
||||
|
||||
final Label titleReport = this.pageService.getWidgetFactory()
|
||||
.labelLocalized(grid, CustomVariant.TEXT_H3, FORM_REPORT_INFO);
|
||||
final GridData gridDataReport = new GridData();
|
||||
gridDataReport.horizontalIndent = 10;
|
||||
gridDataReport.verticalIndent = 10;
|
||||
titleReport.setLayoutData(gridDataReport);
|
||||
|
||||
try {
|
||||
|
||||
// get dependencies
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final RestCall<Set<EntityDependency>>.RestCallBuilder restCallBuilder = this.pageService.getRestService()
|
||||
.getBuilder(GetLmsSetupDependencies.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withQueryParam(API.PARAM_BULK_ACTION_TYPE, BulkActionType.HARD_DELETE.name());
|
||||
|
||||
final Set<EntityDependency> dependencies = restCallBuilder
|
||||
.call()
|
||||
.getOrThrow();
|
||||
final List<EntityDependency> list = dependencies
|
||||
.stream()
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
this.pageService.<EntityDependency> staticListTableBuilder(list, null)
|
||||
.withEmptyMessage(FORM_REPORT_NONE)
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
"FORM_REPORT_LIST_TYPE",
|
||||
FORM_REPORT_LIST_TYPE,
|
||||
dep -> i18nSupport
|
||||
.getText("sebserver.overall.types.entityType." + dep.self.entityType.name())))
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
"FORM_REPORT_LIST_NAME",
|
||||
FORM_REPORT_LIST_NAME,
|
||||
dep -> dep.name))
|
||||
.withColumn(new ColumnDefinition<EntityDependency>(
|
||||
"FORM_REPORT_LIST_DESC",
|
||||
FORM_REPORT_LIST_DESC,
|
||||
dep -> dep.description))
|
||||
.compose(pageContext.copyOf(grid));
|
||||
|
||||
return () -> pageContext;
|
||||
} catch (final Exception e) {
|
||||
log.error("Error while trying to compose LMS Setup delete report page: ", e);
|
||||
pageContext.notifyUnexpectedError(e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -105,11 +105,15 @@ public class LmsSetupForm implements TemplateComposer {
|
|||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final LmsSetupDeletePopup lmsSetupDeletePopup;
|
||||
|
||||
protected LmsSetupForm(final PageService pageService) {
|
||||
protected LmsSetupForm(
|
||||
final PageService pageService,
|
||||
final LmsSetupDeletePopup lmsSetupDeletePopup) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.lmsSetupDeletePopup = lmsSetupDeletePopup;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -305,6 +309,11 @@ public class LmsSetupForm implements TemplateComposer {
|
|||
.withEntityKey(entityKey)
|
||||
.publishIf(() -> modifyGrant && readonly && institutionActive)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_DELETE)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this.lmsSetupDeletePopup.deleteWizardFunction(pageContext))
|
||||
.publishIf(() -> writeGrant && readonly)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_TEST)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(action -> LmsSetupForm.testLmsSetup(action, formHandle, restService))
|
||||
|
|
|
@ -169,7 +169,10 @@ public class LmsSetupList implements TemplateComposer {
|
|||
.publishIf(userGrant::iw)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_VIEW_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publish(false)
|
||||
|
||||
.newAction(ActionDefinition.LMS_SETUP_MODIFY_FROM_LIST)
|
||||
|
|
|
@ -25,11 +25,13 @@ import org.springframework.stereotype.Component;
|
|||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringFeature;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.ProctoringServiceSettings.ProctoringServerType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.Form;
|
||||
|
@ -44,15 +46,17 @@ import ch.ethz.seb.sebserver.gui.service.page.event.ActionEvent;
|
|||
import ch.ethz.seb.sebserver.gui.service.page.impl.ModalInputDialog;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamTemplateProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.SaveExamTemplateProctoringSettings;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class ExamProctoringSettings {
|
||||
public class ProctoringSettingsPopup {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(ExamProctoringSettings.class);
|
||||
private static final Logger log = LoggerFactory.getLogger(ProctoringSettingsPopup.class);
|
||||
|
||||
private final static LocTextKey SEB_PROCTORING_FORM_TITLE =
|
||||
new LocTextKey("sebserver.exam.proctoring.form.title");
|
||||
|
@ -90,12 +94,13 @@ public class ExamProctoringSettings {
|
|||
.withAttribute(
|
||||
PageContext.AttributeKeys.FORCE_READ_ONLY,
|
||||
(modifyGrant) ? Constants.FALSE_STRING : Constants.TRUE_STRING);
|
||||
|
||||
final ModalInputDialog<FormHandle<?>> dialog =
|
||||
new ModalInputDialog<FormHandle<?>>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
pageService.getWidgetFactory())
|
||||
.setDialogWidth(740)
|
||||
.setDialogHeight(400);
|
||||
.setDialogWidth(860)
|
||||
.setDialogHeight(600);
|
||||
|
||||
final SEBProctoringPropertiesForm bindFormContext = new SEBProctoringPropertiesForm(
|
||||
pageService,
|
||||
|
@ -176,18 +181,26 @@ public class ExamProctoringSettings {
|
|||
return false;
|
||||
}
|
||||
|
||||
final boolean saveOk = !pageService
|
||||
final Result<ProctoringServiceSettings> settings = pageService
|
||||
.getRestService()
|
||||
.getBuilder(SaveProctoringSettings.class)
|
||||
.getBuilder(
|
||||
entityKey.entityType == EntityType.EXAM
|
||||
? SaveExamProctoringSettings.class
|
||||
: SaveExamTemplateProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.withBody(examProctoring)
|
||||
.call()
|
||||
.call();
|
||||
|
||||
final boolean saveOk = !settings
|
||||
.onError(formHandle::handleError)
|
||||
.hasError();
|
||||
|
||||
if (saveOk) {
|
||||
final PageAction action = pageService.pageActionBuilder(pageContext)
|
||||
.newAction(ActionDefinition.EXAM_VIEW_FROM_LIST)
|
||||
.newAction(
|
||||
entityKey.entityType == EntityType.EXAM
|
||||
? ActionDefinition.EXAM_VIEW_FROM_LIST
|
||||
: ActionDefinition.EXAM_TEMPLATE_VIEW_FROM_LIST)
|
||||
.create();
|
||||
|
||||
pageService.firePageEvent(
|
||||
|
@ -227,7 +240,10 @@ public class ExamProctoringSettings {
|
|||
.createPopupScrollComposite(parent);
|
||||
|
||||
final ProctoringServiceSettings proctoringSettings = restService
|
||||
.getBuilder(GetProctoringSettings.class)
|
||||
.getBuilder(
|
||||
entityKey.entityType == EntityType.EXAM
|
||||
? GetExamProctoringSettings.class
|
||||
: GetExamTemplateProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
@ -329,24 +345,6 @@ public class ExamProctoringSettings {
|
|||
|
||||
return () -> formHandle;
|
||||
}
|
||||
|
||||
// TODO
|
||||
// private void procServiceSelection(final Form form) {
|
||||
// final ProctoringServerType proctoringServerType = ProctoringServerType
|
||||
// .valueOf(form.getFieldValue(ProctoringServiceSettings.ATTR_SERVER_TYPE));
|
||||
// switch (proctoringServerType) {
|
||||
// case ZOOM: {
|
||||
// form.setFieldVisible(true, ProctoringServiceSettings.ATTR_SDK_KEY);
|
||||
// form.setFieldVisible(true, ProctoringServiceSettings.ATTR_SDK_SECRET);
|
||||
// break;
|
||||
// }
|
||||
// default: {
|
||||
// form.setFieldVisible(false, ProctoringServiceSettings.ATTR_SDK_KEY);
|
||||
// form.setFieldVisible(false, ProctoringServiceSettings.ATTR_SDK_SECRET);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -236,7 +236,7 @@ public class QuizLookupList implements TemplateComposer {
|
|||
actionBuilder
|
||||
.newAction(ActionDefinition.QUIZ_DISCOVERY_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
table::getMultiSelection,
|
||||
action -> this.showDetails(
|
||||
action,
|
||||
table.getSingleSelectedROWData(),
|
||||
|
|
|
@ -0,0 +1,180 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.monitoring;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
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;
|
||||
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.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetFinishedExamClientConnectionPage;
|
||||
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.TableBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.table.TableFilter.CriteriaType;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class FinishedExam implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.emptySelection");
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connections.title");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connections.empty");
|
||||
private static final LocTextKey TABLE_COLUMN_NAME =
|
||||
new LocTextKey("sebserver.finished.exam.connections.name");
|
||||
private static final LocTextKey TABLE_COLUMN_INFO =
|
||||
new LocTextKey("sebserver.finished.exam.connections.info");
|
||||
private static final LocTextKey TABLE_COLUMN_STATUS =
|
||||
new LocTextKey("sebserver.finished.exam.connections.status");
|
||||
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.FILTER_ATTR_SESSION_ID);
|
||||
private final TableFilterAttribute infoFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, ClientConnection.ATTR_INFO);
|
||||
private final TableFilterAttribute statusFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final RestService restService;
|
||||
private final int pageSize;
|
||||
|
||||
public FinishedExam(
|
||||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.restService = pageService.getRestService();
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.statusFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
ClientConnection.FILTER_ATTR_STATUS,
|
||||
pageService.getResourceService()::localizedClientConnectionStatusResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final EntityKey examKey = pageContext.getEntityKey();
|
||||
final CurrentUser currentUser = this.pageService.getResourceService().getCurrentUser();
|
||||
final UserInfo user = currentUser.get();
|
||||
|
||||
final RestService restService = this.pageService
|
||||
.getRestService();
|
||||
final PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(pageContext.clearEntityKeys());
|
||||
final Collection<Indicator> indicators = restService
|
||||
.getBuilder(GetIndicators.class)
|
||||
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, examKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
final Exam exam = this.restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, examKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
final boolean supporting = user.hasRole(UserRole.EXAM_SUPPORTER) &&
|
||||
exam.supporter.contains(user.uuid);
|
||||
final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
|
||||
|
||||
final Composite content = this.pageService.getWidgetFactory().defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
new LocTextKey(TITLE_TEXT_KEY.name, exam.getName()));
|
||||
|
||||
final TableBuilder<ClientConnectionData> tableBuilder =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetFinishedExamClientConnectionPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withStaticFilter(ClientConnection.FILTER_ATTR_EXAM_ID, examKey.modelId)
|
||||
|
||||
.withColumn(new ColumnDefinition<ClientConnectionData>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
TABLE_COLUMN_NAME,
|
||||
c -> c.clientConnection.getUserSessionId())
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<ClientConnectionData>(
|
||||
ClientConnection.ATTR_INFO,
|
||||
TABLE_COLUMN_INFO,
|
||||
c -> c.clientConnection.getInfo())
|
||||
.withFilter(this.infoFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<ClientConnectionData>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
TABLE_COLUMN_STATUS,
|
||||
row -> this.pageService.getResourceService()
|
||||
.localizedClientConnectionStatusName(row.clientConnection.getStatus()))
|
||||
.withFilter(this.statusFilter)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(examKey)
|
||||
.create())
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION));
|
||||
|
||||
indicators.stream().forEach(indicator -> {
|
||||
if (indicator.type == IndicatorType.LAST_PING || indicator.type == IndicatorType.NONE) {
|
||||
return;
|
||||
}
|
||||
tableBuilder.withColumn(new ColumnDefinition<ClientConnectionData>(
|
||||
ClientConnectionData.ATTR_INDICATOR_VALUE + Constants.UNDERLINE + indicator.id,
|
||||
new LocTextKey(indicator.name),
|
||||
cc -> cc.getIndicatorDisplayValue(indicator))
|
||||
.sortable());
|
||||
});
|
||||
|
||||
final EntityTable<ClientConnectionData> table = tableBuilder.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(examKey)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(isExamSupporter, false);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,289 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.monitoring;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.function.BooleanSupplier;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.IndicatorType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.QuizData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnectionData;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ExtendedClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.IndicatorValue;
|
||||
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.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionDefinition;
|
||||
import ch.ethz.seb.sebserver.gui.form.FormBuilder;
|
||||
import ch.ethz.seb.sebserver.gui.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.TemplateComposer;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetFinishedExamClientConnection;
|
||||
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.TableFilter.CriteriaType;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class FinishedExamClientConnection implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey PAGE_TITLE_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.title");
|
||||
|
||||
private final static LocTextKey EXAM_NAME_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.connection.form.exam");
|
||||
private final static LocTextKey CONNECTION_ID_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.connection.form.id");
|
||||
private final static LocTextKey CONNECTION_INFO_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.connection.form.info");
|
||||
private final static LocTextKey CONNECTION_STATUS_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.connection.form.status");
|
||||
|
||||
private static final LocTextKey EVENT_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.eventlist.title");
|
||||
private static final LocTextKey EVENT_LIST_TITLE_TOOLTIP_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.eventlist.title.tooltip");
|
||||
private static final LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.eventlist.empty");
|
||||
private static final LocTextKey LIST_COLUMN_TYPE_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.eventlist.type");
|
||||
|
||||
private static final LocTextKey LIST_COLUMN_CLIENT_TIME_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.eventlist.clienttime");
|
||||
private static final LocTextKey LIST_COLUMN_SERVER_TIME_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.eventlist.servertime");
|
||||
private static final LocTextKey LIST_COLUMN_VALUE_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.eventlist.value");
|
||||
private static final LocTextKey LIST_COLUMN_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.connection.eventlist.text");
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final SEBClientEventDetailsPopup sebClientLogDetailsPopup;
|
||||
private final int pageSize;
|
||||
|
||||
private final TableFilterAttribute typeFilter;
|
||||
private final TableFilterAttribute textFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, ClientEvent.FILTER_ATTR_TEXT);
|
||||
|
||||
protected FinishedExamClientConnection(
|
||||
final PageService pageService,
|
||||
final SEBClientEventDetailsPopup sebClientLogDetailsPopup,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.i18nSupport = this.resourceService.getI18nSupport();
|
||||
this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Domain.CLIENT_EVENT.ATTR_TYPE,
|
||||
this.resourceService::clientEventTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
PAGE_TITLE_KEY);
|
||||
final Exam exam = restService
|
||||
.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> pageContext.notifyLoadError(EntityType.EXAM, error))
|
||||
.getOrThrow();
|
||||
final UserInfo user = currentUser.get();
|
||||
final boolean supporting = user.hasRole(UserRole.EXAM_SUPPORTER) &&
|
||||
exam.supporter.contains(user.uuid);
|
||||
final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
|
||||
final Collection<Indicator> indicators = restService
|
||||
.getBuilder(GetIndicators.class)
|
||||
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
final ClientConnectionData connectionData = restService
|
||||
.getBuilder(GetFinishedExamClientConnection.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
||||
final FormBuilder formBuilder = this.pageService.formBuilder(pageContext.copyOf(content))
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
EXAM_NAME_TEXT_KEY,
|
||||
exam.getName()))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
CONNECTION_ID_TEXT_KEY,
|
||||
connectionData.clientConnection.userSessionId))
|
||||
.addField(FormBuilder.text(
|
||||
ClientConnection.ATTR_INFO,
|
||||
CONNECTION_INFO_TEXT_KEY,
|
||||
connectionData.clientConnection.info))
|
||||
.withDefaultSpanInput(3)
|
||||
.addField(FormBuilder.text(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
CONNECTION_STATUS_TEXT_KEY,
|
||||
this.resourceService.localizedClientConnectionStatusName(
|
||||
connectionData.clientConnection.status))
|
||||
.asColorBox())
|
||||
.addEmptyCell();
|
||||
|
||||
indicators.forEach(indicator -> {
|
||||
if (indicator.type == IndicatorType.LAST_PING || indicator.type == IndicatorType.NONE) {
|
||||
return;
|
||||
}
|
||||
formBuilder.addField(FormBuilder.text(
|
||||
indicator.name,
|
||||
new LocTextKey(indicator.name),
|
||||
connectionData.indicatorValues
|
||||
.stream()
|
||||
.filter(indicatorValue -> indicatorValue.getIndicatorId().equals(indicator.id))
|
||||
.findFirst()
|
||||
.map(iv -> IndicatorValue.getDisplayValue(iv, indicator.type))
|
||||
.orElse(Constants.EMPTY_NOTE))
|
||||
.asColorBox()
|
||||
.withDefaultLabel(indicator.name))
|
||||
.addEmptyCell();
|
||||
});
|
||||
|
||||
formBuilder.build();
|
||||
|
||||
// CLIENT EVENTS
|
||||
final PageService.PageActionBuilder actionBuilder = this.pageService
|
||||
.pageActionBuilder(
|
||||
pageContext
|
||||
.clearAttributes()
|
||||
.clearEntityKeys());
|
||||
|
||||
widgetFactory.addFormSubContextHeader(
|
||||
content,
|
||||
EVENT_LIST_TITLE_KEY,
|
||||
EVENT_LIST_TITLE_TOOLTIP_KEY);
|
||||
|
||||
// client event table for this connection
|
||||
this.pageService
|
||||
.entityTableBuilder(
|
||||
"seb-client-" + connectionData.getModelId(),
|
||||
restService.getRestCall(GetExtendedClientEventPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRestCallAdapter(restCallBuilder -> restCallBuilder.withQueryParam(
|
||||
ClientEvent.FILTER_ATTR_CONNECTION_ID,
|
||||
entityKey.modelId))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_TYPE,
|
||||
LIST_COLUMN_TYPE_KEY,
|
||||
this.resourceService::getEventTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_TEXT,
|
||||
LIST_COLUMN_TEXT_KEY,
|
||||
ClientEvent::getText)
|
||||
.withFilter(this.textFilter)
|
||||
.sortable()
|
||||
.withCellTooltip()
|
||||
.widthProportion(4))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_NUMERIC_VALUE,
|
||||
LIST_COLUMN_VALUE_KEY,
|
||||
ClientEvent::getValue)
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_CLIENT_TIME,
|
||||
new LocTextKey(LIST_COLUMN_CLIENT_TIME_KEY.name,
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
this::getClientTime)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<ExtendedClientEvent>(
|
||||
Domain.CLIENT_EVENT.ATTR_SERVER_TIME,
|
||||
new LocTextKey(LIST_COLUMN_SERVER_TIME_KEY.name,
|
||||
this.i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
this::getServerTime)
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_SEB_CLIENT_SHOW_DETAILS)
|
||||
.withExec(action -> this.sebClientLogDetailsPopup.showDetails(action,
|
||||
t.getSingleSelectedROWData()))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.FINISHED_EXAM_BACK_TO_OVERVIEW)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.publishIf(isExamSupporter);
|
||||
}
|
||||
|
||||
private String getClientTime(final ClientEvent event) {
|
||||
if (event == null || event.getClientTime() == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayTime(Utils.toDateTimeUTC(event.getClientTime()));
|
||||
}
|
||||
|
||||
private String getServerTime(final ClientEvent event) {
|
||||
if (event == null || event.getServerTime() == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayTime(Utils.toDateTimeUTC(event.getServerTime()));
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,167 @@
|
|||
/*
|
||||
* Copyright (c) 2022 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.monitoring;
|
||||
|
||||
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.content.exam.ExamList;
|
||||
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.GetFinishedExamPage;
|
||||
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 FinishedExamList implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey PAGE_TITLE_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.list.title");
|
||||
private final static LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.info.pleaseSelect");
|
||||
private final static LocTextKey COLUMN_TITLE_NAME_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.list.column.name");
|
||||
private final static LocTextKey COLUMN_TITLE_STATE_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.list.column.state");
|
||||
private final static LocTextKey COLUMN_TITLE_TYPE_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.list.column.type");
|
||||
private final static LocTextKey EMPTY_LIST_TEXT_KEY =
|
||||
new LocTextKey("sebserver.finished.exam.list.empty");
|
||||
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
private final TableFilterAttribute typeFilter;
|
||||
private final TableFilterAttribute stateFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final int pageSize;
|
||||
|
||||
protected FinishedExamList(
|
||||
final PageService pageService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = pageService.getResourceService();
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Exam.FILTER_ATTR_TYPE,
|
||||
this.resourceService::examTypeResources);
|
||||
|
||||
this.stateFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
Exam.FILTER_ATTR_STATUS,
|
||||
this.resourceService::localizedFinishedExamStatusSelection);
|
||||
}
|
||||
|
||||
@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<Exam> table =
|
||||
this.pageService.entityTableBuilder(restService.getRestCall(GetFinishedExamPage.class))
|
||||
.withEmptyMessage(EMPTY_LIST_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
.withRowDecorator(ExamList.decorateOnExamConsistency(this.pageService))
|
||||
.withDefaultSort(QuizData.QUIZ_ATTR_NAME)
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_NAME,
|
||||
COLUMN_TITLE_NAME_KEY,
|
||||
Exam::getName)
|
||||
.withFilter(this.nameFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_STATUS,
|
||||
COLUMN_TITLE_STATE_KEY,
|
||||
this.resourceService::localizedExamStatusName)
|
||||
.withFilter(this.stateFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<Exam>(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
COLUMN_TITLE_TYPE_KEY,
|
||||
this.resourceService::localizedExamTypeName)
|
||||
.withFilter(this.typeFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_START_TIME,
|
||||
new LocTextKey(
|
||||
"sebserver.finished.exam.list.column.startTime",
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getStartTime)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
QuizData.QUIZ_ATTR_END_TIME,
|
||||
new LocTextKey(
|
||||
"sebserver.finished.exam.list.column.endTime",
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getEndTime)
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(actionBuilder
|
||||
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST)
|
||||
.create())
|
||||
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
pageContext,
|
||||
ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST))
|
||||
|
||||
.compose(pageContext.copyOf(content));
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.VIEW_FINISHED_EXAM_FROM_LIST)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -8,7 +8,9 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content.monitoring;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
|
@ -52,8 +54,8 @@ import ch.ethz.seb.sebserver.gui.service.push.UpdateErrorHandler;
|
|||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.logs.GetExtendedClientEventPage;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.ConfirmPendingClientNotification;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.session.GetClientConnectionData;
|
||||
|
@ -90,6 +92,11 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
private static final LocTextKey NOTIFICATION_LIST_COLUMN_TYPE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.notificationlist.type");
|
||||
|
||||
private static final LocTextKey CONFIRM_QUIT =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
|
||||
private static final LocTextKey CONFIRM_OPEN_SINGLE_ROOM =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.singleroom.confirm");
|
||||
|
||||
private static final LocTextKey EVENT_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.title");
|
||||
private static final LocTextKey EVENT_LIST_TITLE_TOOLTIP_KEY =
|
||||
|
@ -107,10 +114,6 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.value");
|
||||
private static final LocTextKey LIST_COLUMN_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.eventlist.text");
|
||||
private static final LocTextKey CONFIRM_QUIT =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.instruction.quit.confirm");
|
||||
private static final LocTextKey CONFIRM_OPEN_SINGLE_ROOM =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.action.singleroom.confirm");
|
||||
|
||||
private final ServerPushService serverPushService;
|
||||
private final PageService pageService;
|
||||
|
@ -118,6 +121,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
private final I18nSupport i18nSupport;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
private final SEBClientEventDetailsPopup sebClientLogDetailsPopup;
|
||||
private final SEBSendLockPopup sebSendLockPopup;
|
||||
private final MonitoringProctoringService monitoringProctoringService;
|
||||
private final long pollInterval;
|
||||
private final int pageSize;
|
||||
|
@ -132,6 +136,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
final InstructionProcessor instructionProcessor,
|
||||
final SEBClientEventDetailsPopup sebClientLogDetailsPopup,
|
||||
final MonitoringProctoringService monitoringProctoringService,
|
||||
final SEBSendLockPopup sebSendLockPopup,
|
||||
@Value("${sebserver.gui.webservice.poll-interval:500}") final long pollInterval,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
|
@ -143,6 +148,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
this.monitoringProctoringService = monitoringProctoringService;
|
||||
this.pollInterval = pollInterval;
|
||||
this.sebClientLogDetailsPopup = sebClientLogDetailsPopup;
|
||||
this.sebSendLockPopup = sebSendLockPopup;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.typeFilter = new TableFilterAttribute(
|
||||
|
@ -263,7 +269,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
.withParentEntityKey(parentEntityKey)
|
||||
.withConfirm(() -> NOTIFICATION_LIST_CONFIRM_TEXT_KEY)
|
||||
.withSelect(
|
||||
() -> notificationTable.getSelection(),
|
||||
() -> notificationTable.getMultiSelection(),
|
||||
action -> this.confirmNotification(action, connectionData, notificationTable),
|
||||
|
||||
NOTIFICATION_LIST_NO_SELECTION_KEY)
|
||||
|
@ -275,14 +281,11 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
|
||||
final Supplier<EntityTable<ClientNotification>> notificationTableSupplier = _notificationTableSupplier;
|
||||
// server push update
|
||||
final UpdateErrorHandler updateErrorHandler =
|
||||
new UpdateErrorHandler(this.pageService, pageContext);
|
||||
|
||||
this.serverPushService.runServerPush(
|
||||
new ServerPushContext(
|
||||
content,
|
||||
Utils.truePredicate(),
|
||||
updateErrorHandler),
|
||||
new UpdateErrorHandler(this.pageService, pageContext)),
|
||||
this.pollInterval,
|
||||
context -> clientConnectionDetails.updateData(),
|
||||
context -> clientConnectionDetails.updateGUI(notificationTableSupplier, pageContext));
|
||||
|
@ -367,18 +370,26 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
.withConfirm(() -> CONFIRM_QUIT)
|
||||
.withExec(action -> {
|
||||
this.instructionProcessor.propagateSEBQuitInstruction(
|
||||
exam.id,
|
||||
exam.getModelId(),
|
||||
connectionToken,
|
||||
pageContext);
|
||||
return action;
|
||||
})
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> isExamSupporter.getAsBoolean() &&
|
||||
connectionData.clientConnection.status.clientActiveStatus)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION_LOCK)
|
||||
.withEntityKey(parentEntityKey)
|
||||
.withExec(action -> this.sebSendLockPopup.show(action,
|
||||
some -> new HashSet<>(Arrays.asList(connectionToken))))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> isExamSupporter.getAsBoolean() &&
|
||||
connectionData.clientConnection.status.clientActiveStatus);
|
||||
|
||||
if (connectionData.clientConnection.status == ConnectionStatus.ACTIVE) {
|
||||
final ProctoringServiceSettings proctoringSettings = restService
|
||||
.getBuilder(GetProctoringSettings.class)
|
||||
.getBuilder(GetExamProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, parentEntityKey.modelId)
|
||||
.call()
|
||||
.onError(error -> log.error("Failed to get ProctoringServiceSettings", error))
|
||||
|
|
|
@ -69,7 +69,7 @@ public class MonitoringExamSearchPopup {
|
|||
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
|
||||
pageContext.getParent().getShell(),
|
||||
this.pageService.getWidgetFactory());
|
||||
dialog.setLargeDialogWidth();
|
||||
dialog.setVeryLargeDialogWidth();
|
||||
dialog.setDialogHeight(380);
|
||||
dialog.open(
|
||||
TITLE_TEXT_KEY,
|
||||
|
@ -93,20 +93,23 @@ public class MonitoringExamSearchPopup {
|
|||
Domain.CLIENT_CONNECTION.ATTR_EXAM_USER_SESSION_ID,
|
||||
TABLE_COLUMN_NAME,
|
||||
ClientConnection::getUserSessionId)
|
||||
.withFilter(this.nameFilter))
|
||||
.withFilter(this.nameFilter)
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
ClientConnection.ATTR_INFO,
|
||||
TABLE_COLUMN_INFO,
|
||||
ClientConnection::getInfo)
|
||||
.withFilter(this.infoFilter))
|
||||
.withFilter(this.infoFilter)
|
||||
.widthProportion(3))
|
||||
|
||||
.withColumn(new ColumnDefinition<ClientConnection>(
|
||||
Domain.CLIENT_CONNECTION.ATTR_STATUS,
|
||||
TABLE_COLUMN_STATUS,
|
||||
row -> this.pageService.getResourceService()
|
||||
.localizedClientConnectionStatusName(row.getStatus()))
|
||||
.withFilter(this.statusFilter))
|
||||
.withFilter(this.statusFilter)
|
||||
.widthProportion(1))
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.util.Collection;
|
|||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.eclipse.swt.SWT;
|
||||
|
@ -54,8 +55,8 @@ import ch.ethz.seb.sebserver.gui.service.page.impl.PageAction;
|
|||
import ch.ethz.seb.sebserver.gui.service.push.ServerPushService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExam;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetExamProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetIndicators;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.exam.GetProctoringSettings;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.ClientConnectionTable;
|
||||
import ch.ethz.seb.sebserver.gui.service.session.FullPageMonitoringGUIUpdate;
|
||||
|
@ -70,8 +71,6 @@ import ch.ethz.seb.sebserver.gui.service.session.proctoring.ProctoringGUIService
|
|||
@GuiProfile
|
||||
public class MonitoringRunningExam implements TemplateComposer {
|
||||
|
||||
//private static final Logger log = LoggerFactory.getLogger(MonitoringRunningExam.class);
|
||||
|
||||
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.monitoring.exam.connection.emptySelection");
|
||||
private static final LocTextKey EMPTY_ACTIVE_SELECTION_TEXT_KEY =
|
||||
|
@ -94,16 +93,18 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
private final AsyncRunner asyncRunner;
|
||||
private final InstructionProcessor instructionProcessor;
|
||||
private final MonitoringExamSearchPopup monitoringExamSearchPopup;
|
||||
private final SEBSendLockPopup sebSendLockPopup;
|
||||
private final MonitoringProctoringService monitoringProctoringService;
|
||||
private final boolean distributedSetup;
|
||||
private final long pollInterval;
|
||||
|
||||
protected MonitoringRunningExam(
|
||||
public MonitoringRunningExam(
|
||||
final ServerPushService serverPushService,
|
||||
final PageService pageService,
|
||||
final AsyncRunner asyncRunner,
|
||||
final InstructionProcessor instructionProcessor,
|
||||
final MonitoringExamSearchPopup monitoringExamSearchPopup,
|
||||
final SEBSendLockPopup sebSendLockPopup,
|
||||
final MonitoringProctoringService monitoringProctoringService,
|
||||
final GuiServiceInfo guiServiceInfo,
|
||||
@Value("${sebserver.gui.webservice.poll-interval:2000}") final long pollInterval) {
|
||||
|
@ -118,14 +119,14 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
this.pollInterval = pollInterval;
|
||||
this.distributedSetup = guiServiceInfo.isDistributedSetup();
|
||||
this.monitoringExamSearchPopup = monitoringExamSearchPopup;
|
||||
this.sebSendLockPopup = sebSendLockPopup;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
final EntityKey entityKey = pageContext.getEntityKey();
|
||||
final CurrentUser currentUser = this.resourceService.getCurrentUser();
|
||||
final Exam exam = restService.getBuilder(GetExam.class)
|
||||
final Exam exam = this.restService.getBuilder(GetExam.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
@ -134,7 +135,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
exam.supporter.contains(user.uuid);
|
||||
final BooleanSupplier isExamSupporter = () -> supporting || user.hasRole(UserRole.EXAM_ADMIN);
|
||||
|
||||
final Collection<Indicator> indicators = restService.getBuilder(GetIndicators.class)
|
||||
final Collection<Indicator> indicators = this.restService.getBuilder(GetIndicators.class)
|
||||
.withQueryParam(Indicator.FILTER_ATTR_EXAM_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOrThrow();
|
||||
|
@ -175,15 +176,48 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
.withParentEntityKey(entityKey)
|
||||
.create(),
|
||||
this.pageService)
|
||||
.withSelectionListener(this.pageService.getSelectionPublisher(
|
||||
.withSelectionListener(this.getSelectionPublisherClientConnectionTable(
|
||||
pageContext,
|
||||
ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION,
|
||||
ActionDefinition.MONITOR_EXAM_QUIT_SELECTED,
|
||||
ActionDefinition.MONITOR_EXAM_LOCK_SELECTED,
|
||||
ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION,
|
||||
ActionDefinition.MONITOR_EXAM_NEW_PROCTOR_ROOM));
|
||||
|
||||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this::openSearchPopup)
|
||||
.noEventPropagation()
|
||||
.publishIf(isExamSupporter)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_ALL)
|
||||
.withExec(action -> this.quitSEBClients(action, clientTable, true))
|
||||
.noEventPropagation()
|
||||
.publishIf(isExamSupporter)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_SELECTED)
|
||||
.withSelect(
|
||||
() -> this.selectionForInstruction(clientTable),
|
||||
action -> this.quitSEBClients(action, clientTable, false),
|
||||
EMPTY_ACTIVE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(isExamSupporter, false)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_LOCK_SELECTED)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
() -> this.selectionForInstruction(clientTable),
|
||||
action -> this.showSEBLockActionPopup(action, clientTable),
|
||||
EMPTY_ACTIVE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(isExamSupporter, false)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_CLIENT_CONNECTION)
|
||||
.withParentEntityKey(entityKey)
|
||||
.withExec(pageAction -> {
|
||||
|
@ -204,29 +238,6 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
})
|
||||
.publishIf(isExamSupporter, false)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_ALL)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_ALL)
|
||||
.withExec(action -> this.quitSEBClients(action, clientTable, true))
|
||||
.noEventPropagation()
|
||||
.publishIf(isExamSupporter)
|
||||
|
||||
.newAction(ActionDefinition.MONITORING_EXAM_SEARCH_CONNECTIONS)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(this::openSearchPopup)
|
||||
.noEventPropagation()
|
||||
.publishIf(isExamSupporter)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_QUIT_SELECTED)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_QUIT_SELECTED)
|
||||
.withSelect(
|
||||
() -> this.selectionForQuitInstruction(clientTable),
|
||||
action -> this.quitSEBClients(action, clientTable, false),
|
||||
EMPTY_ACTIVE_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(isExamSupporter, false)
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_DISABLE_SELECTED_CONNECTION)
|
||||
.withEntityKey(entityKey)
|
||||
.withConfirm(() -> CONFIRM_DISABLE_SELECTED)
|
||||
|
@ -245,7 +256,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
isExamSupporter));
|
||||
|
||||
final ProctoringServiceSettings proctoringSettings = this.restService
|
||||
.getBuilder(GetProctoringSettings.class)
|
||||
.getBuilder(GetExamProctoringSettings.class)
|
||||
.withURIVariable(API.PARAM_MODEL_ID, entityKey.modelId)
|
||||
.call()
|
||||
.getOr(null);
|
||||
|
@ -264,6 +275,19 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
fullPageMonitoringUpdate.start(pageContext, content, this.pollInterval);
|
||||
}
|
||||
|
||||
private PageAction showSEBLockActionPopup(
|
||||
final PageAction action,
|
||||
final ClientConnectionTable clientTable) {
|
||||
|
||||
this.sebSendLockPopup.show(
|
||||
action,
|
||||
statesPredicate -> clientTable.getConnectionTokens(
|
||||
statesPredicate,
|
||||
true));
|
||||
clientTable.removeSelection();
|
||||
return action;
|
||||
}
|
||||
|
||||
private FullPageMonitoringGUIUpdate createProctoringActions(
|
||||
final ProctoringServiceSettings proctoringSettings,
|
||||
final ProctoringGUIService proctoringGUIService,
|
||||
|
@ -463,7 +487,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
};
|
||||
}
|
||||
|
||||
private Set<EntityKey> selectionForQuitInstruction(final ClientConnectionTable clientTable) {
|
||||
private Set<EntityKey> selectionForInstruction(final ClientConnectionTable clientTable) {
|
||||
final Set<String> connectionTokens = clientTable.getConnectionTokens(
|
||||
cc -> cc.status.clientActiveStatus,
|
||||
true);
|
||||
|
@ -480,7 +504,7 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
final boolean all) {
|
||||
|
||||
this.instructionProcessor.propagateSEBQuitInstruction(
|
||||
clientTable.getExam().id,
|
||||
clientTable.getExam().getModelId(),
|
||||
statesPredicate -> clientTable.getConnectionTokens(
|
||||
statesPredicate,
|
||||
!all),
|
||||
|
@ -508,4 +532,13 @@ public class MonitoringRunningExam implements TemplateComposer {
|
|||
return action;
|
||||
}
|
||||
|
||||
private Consumer<ClientConnectionTable> getSelectionPublisherClientConnectionTable(
|
||||
final PageContext pageContext,
|
||||
final ActionDefinition... actionDefinitions) {
|
||||
|
||||
return table -> this.pageService.firePageEvent(
|
||||
new ActionActivationEvent(table.getSingleSelection() != null, actionDefinitions),
|
||||
pageContext);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -141,7 +141,10 @@ public class MonitoringRunningExamList implements TemplateComposer {
|
|||
actionBuilder
|
||||
|
||||
.newAction(ActionDefinition.MONITOR_EXAM_FROM_LIST)
|
||||
.withSelect(table::getSelection, PageAction::applySingleSelectionAsEntityKey, EMPTY_SELECTION_TEXT_KEY)
|
||||
.withSelect(
|
||||
table::getMultiSelection,
|
||||
PageAction::applySingleSelectionAsEntityKey,
|
||||
EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> currentUser.get().hasRole(UserRole.EXAM_SUPPORTER), false);
|
||||
|
||||
}
|
||||
|
|