SEBSERV-26 user activity logs
This commit is contained in:
parent
86720923c5
commit
e5f8a995e6
54 changed files with 1158 additions and 372 deletions
|
@ -187,6 +187,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter implements E
|
|||
final String httpMethod) throws IOException {
|
||||
|
||||
super.prepareConnection(connection, httpMethod);
|
||||
super.setBufferRequestBody(false);
|
||||
connection.setInstanceFollowRedirects(false);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gbl.async;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
|
||||
public class AsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
|
||||
|
||||
private static final Logger log = LoggerFactory.getLogger(AsyncExceptionHandler.class);
|
||||
|
||||
public AsyncExceptionHandler() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUncaughtException(final Throwable ex, final Method method, final Object... params) {
|
||||
log.error("Unexpected error while async processing. method: {}", method, ex);
|
||||
throw new RuntimeException("Unexpected error while async processing: ", ex);
|
||||
}
|
||||
|
||||
}
|
|
@ -10,6 +10,7 @@ package ch.ethz.seb.sebserver.gbl.async;
|
|||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.scheduling.annotation.AsyncConfigurer;
|
||||
|
@ -38,4 +39,9 @@ public class AsyncServiceSpringConfig implements AsyncConfigurer {
|
|||
return threadPoolTaskExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
|
||||
return new AsyncExceptionHandler();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
package ch.ethz.seb.sebserver.gbl.model.user;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
|
@ -18,16 +17,21 @@ import ch.ethz.seb.sebserver.gbl.model.Entity;
|
|||
|
||||
public class UserActivityLog implements Entity {
|
||||
|
||||
public static final String ATTR_USER_NAME = "username";
|
||||
public static final String FILTER_ATTR_USER = "user";
|
||||
public static final String FILTER_ATTR_FROM = "from";
|
||||
public static final String FILTER_ATTR_TO = "to";
|
||||
public static final String FILTER_ATTR_FROM_TO = "from_to";
|
||||
|
||||
public static final String FILTER_ATTR_ACTIVITY_TYPES = "activity_types";
|
||||
public static final String FILTER_ATTR_ENTITY_TYPES = "entity_types";
|
||||
|
||||
@JsonIgnore
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_ID)
|
||||
public final Long id;
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_USER_UUID)
|
||||
public final String userUUID;
|
||||
@JsonProperty(ATTR_USER_NAME)
|
||||
public final String username;
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_TIMESTAMP)
|
||||
public final Long timestamp;
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_ACTIVITY_TYPE)
|
||||
|
@ -41,33 +45,18 @@ public class UserActivityLog implements Entity {
|
|||
|
||||
@JsonCreator
|
||||
public UserActivityLog(
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_ID) final Long id,
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_USER_UUID) final String userUUID,
|
||||
@JsonProperty(ATTR_USER_NAME) final String username,
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_TIMESTAMP) final Long timestamp,
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_ACTIVITY_TYPE) final UserLogActivityType activityType,
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_ENTITY_TYPE) final EntityType entityType,
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_ENTITY_ID) final String entityId,
|
||||
@JsonProperty(USER_ACTIVITY_LOG.ATTR_MESSAGE) final String message) {
|
||||
|
||||
this.id = null;
|
||||
this.userUUID = userUUID;
|
||||
this.timestamp = timestamp;
|
||||
this.activityType = activityType;
|
||||
this.entityType = entityType;
|
||||
this.entityId = entityId;
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public UserActivityLog(
|
||||
final Long id,
|
||||
final String userUUID,
|
||||
final Long timestamp,
|
||||
final UserLogActivityType activityType,
|
||||
final EntityType entityType,
|
||||
final String entityId,
|
||||
final String message) {
|
||||
|
||||
this.id = id;
|
||||
this.userUUID = userUUID;
|
||||
this.username = username;
|
||||
this.timestamp = timestamp;
|
||||
this.activityType = activityType;
|
||||
this.entityType = entityType;
|
||||
|
@ -89,13 +78,17 @@ public class UserActivityLog implements Entity {
|
|||
|
||||
@Override
|
||||
public String getName() {
|
||||
return getModelId();
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public String getUserUuid() {
|
||||
return this.userUUID;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return this.username;
|
||||
}
|
||||
|
||||
public Long getTimestamp() {
|
||||
return this.timestamp;
|
||||
}
|
||||
|
@ -123,6 +116,8 @@ public class UserActivityLog implements Entity {
|
|||
builder.append(this.id);
|
||||
builder.append(", userUUID=");
|
||||
builder.append(this.userUUID);
|
||||
builder.append(", username=");
|
||||
builder.append(this.username);
|
||||
builder.append(", timestamp=");
|
||||
builder.append(this.timestamp);
|
||||
builder.append(", activityType=");
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
@ -12,6 +12,7 @@ package ch.ethz.seb.sebserver.gbl.model.user;
|
|||
public enum UserLogActivityType {
|
||||
CREATE,
|
||||
IMPORT,
|
||||
EXPORT,
|
||||
MODIFY,
|
||||
PASSWORD_CHANGE,
|
||||
DEACTIVATE,
|
||||
|
|
|
@ -11,7 +11,9 @@ package ch.ethz.seb.sebserver.gui.content;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.BooleanSupplier;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.apache.commons.lang3.BooleanUtils;
|
||||
import org.apache.tomcat.util.buf.StringUtils;
|
||||
|
@ -23,6 +25,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.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityKey;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
|
@ -102,6 +105,8 @@ public class ExamForm implements TemplateComposer {
|
|||
new LocTextKey("sebserver.exam.configuration.list.column.description");
|
||||
private final static LocTextKey CONFIG_STATUS_COLUMN_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.column.status");
|
||||
private final static LocTextKey CONFIG_EMPTY_SELECTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.exam.configuration.list.pleaseSelect");
|
||||
|
||||
private final static LocTextKey INDICATOR_LIST_TITLE_KEY =
|
||||
new LocTextKey("sebserver.exam.indicator.list.title");
|
||||
|
@ -133,7 +138,7 @@ public class ExamForm implements TemplateComposer {
|
|||
final EntityKey parentEntityKey = pageContext.getParentEntityKey();
|
||||
final boolean readonly = pageContext.isReadonly();
|
||||
final boolean importFromQuizData = BooleanUtils.toBoolean(
|
||||
pageContext.getAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA));
|
||||
pageContext.getAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||
|
||||
// get or create model data
|
||||
final Exam exam = (importFromQuizData
|
||||
|
@ -244,7 +249,7 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(formContext
|
||||
.clearEntityKeys()
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA));
|
||||
.removeAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||
// propagate content actions to action-pane
|
||||
actionBuilder
|
||||
|
||||
|
@ -259,7 +264,7 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
.newAction(ActionDefinition.EXAM_CANCEL_MODIFY)
|
||||
.withEntityKey(entityKey)
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, String.valueOf(importFromQuizData))
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, String.valueOf(importFromQuizData))
|
||||
.withExec(this::cancelModify)
|
||||
.publishIf(() -> !readonly);
|
||||
|
||||
|
@ -318,17 +323,18 @@ public class ExamForm implements TemplateComposer {
|
|||
.newAction(ActionDefinition.EXAM_CONFIGURATION_DELETE_FROM_LIST)
|
||||
.withEntityKey(entityKey)
|
||||
.withSelect(
|
||||
() -> {
|
||||
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
|
||||
if (firstRowData == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return new HashSet<>(Arrays.asList(firstRowData.getEntityKey()));
|
||||
}
|
||||
},
|
||||
getConfigMappingSelection(configurationTable),
|
||||
this::deleteExamConfigMapping,
|
||||
null)
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable);
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant && configurationTable.hasAnyContent() && editable)
|
||||
|
||||
.newAction(ActionDefinition.EXAM_CONFIGURATION_GET_CONFIG_KEY)
|
||||
.withSelect(
|
||||
getConfigSelection(configurationTable),
|
||||
this::getExamConfigKey,
|
||||
CONFIG_EMPTY_SELECTION_TEXT_KEY)
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> userGrantCheck.r() && configurationTable.hasAnyContent());
|
||||
|
||||
// List of Indicators
|
||||
widgetFactory.labelLocalized(
|
||||
|
@ -385,17 +391,35 @@ public class ExamForm implements TemplateComposer {
|
|||
indicatorTable::getSelection,
|
||||
this::deleteSelectedIndicator,
|
||||
INDICATOR_EMPTY_SELECTION_TEXT_KEY)
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent() && editable)
|
||||
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_GET_CONFIG_KEY)
|
||||
.withEntityKey(entityKey)
|
||||
.withExec(SebExamConfigPropForm.getConfigKeyFunction(this.pageService))
|
||||
.noEventPropagation()
|
||||
.publishIf(() -> userGrantCheck.r());
|
||||
;
|
||||
.publishIf(() -> modifyGrant && indicatorTable.hasAnyContent() && editable);
|
||||
}
|
||||
}
|
||||
|
||||
private Supplier<Set<EntityKey>> getConfigMappingSelection(
|
||||
final EntityTable<ExamConfigurationMap> configurationTable) {
|
||||
return () -> {
|
||||
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
|
||||
if (firstRowData == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return new HashSet<>(Arrays.asList(firstRowData.getEntityKey()));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private Supplier<Set<EntityKey>> getConfigSelection(final EntityTable<ExamConfigurationMap> configurationTable) {
|
||||
return () -> {
|
||||
final ExamConfigurationMap firstRowData = configurationTable.getFirstRowData();
|
||||
if (firstRowData == null) {
|
||||
return Collections.emptySet();
|
||||
} else {
|
||||
return new HashSet<>(Arrays.asList(new EntityKey(
|
||||
firstRowData.configurationNodeId,
|
||||
EntityType.CONFIGURATION_NODE)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private PageAction deleteSelectedIndicator(final PageAction action) {
|
||||
final EntityKey indicatorKey = action.getSingleSelection();
|
||||
this.resourceService.getRestService()
|
||||
|
@ -405,6 +429,18 @@ public class ExamForm implements TemplateComposer {
|
|||
return action;
|
||||
}
|
||||
|
||||
private PageAction getExamConfigKey(final PageAction action) {
|
||||
final EntityKey examConfigMappingKey = action.getSingleSelection();
|
||||
if (examConfigMappingKey != null) {
|
||||
action.withEntityKey(examConfigMappingKey);
|
||||
return SebExamConfigPropForm
|
||||
.getConfigKeyFunction(this.pageService)
|
||||
.apply(action);
|
||||
}
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private PageAction deleteExamConfigMapping(final PageAction action) {
|
||||
final EntityKey examConfigMappingKey = action.getSingleSelection();
|
||||
this.resourceService.getRestService()
|
||||
|
@ -457,7 +493,7 @@ public class ExamForm implements TemplateComposer {
|
|||
|
||||
private PageAction cancelModify(final PageAction action) {
|
||||
final boolean importFromQuizData = BooleanUtils.toBoolean(
|
||||
action.pageContext().getAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA));
|
||||
action.pageContext().getAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA));
|
||||
if (importFromQuizData) {
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(action.pageContext());
|
||||
final PageAction activityHomeAction = actionBuilder
|
||||
|
|
|
@ -23,6 +23,7 @@ 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.institution.LmsSetup;
|
||||
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.service.ResourceService;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.I18nSupport;
|
||||
|
@ -68,8 +69,6 @@ public class ExamList implements TemplateComposer {
|
|||
private final TableFilterAttribute lmsFilter;
|
||||
private final TableFilterAttribute nameFilter =
|
||||
new TableFilterAttribute(CriteriaType.TEXT, QuizData.FILTER_ATTR_NAME);
|
||||
private final TableFilterAttribute startTimeFilter =
|
||||
new TableFilterAttribute(CriteriaType.DATE, QuizData.FILTER_ATTR_START_TIME);
|
||||
private final TableFilterAttribute typeFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
|
@ -135,7 +134,12 @@ public class ExamList implements TemplateComposer {
|
|||
"sebserver.exam.list.column.starttime",
|
||||
i18nSupport.getUsersTimeZoneTitleSuffix()),
|
||||
Exam::getStartTime)
|
||||
.withFilter(this.startTimeFilter)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE,
|
||||
QuizData.FILTER_ATTR_START_TIME,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable())
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.EXAM.ATTR_TYPE,
|
||||
|
|
|
@ -30,6 +30,7 @@ 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.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;
|
||||
|
@ -78,6 +79,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
private final ServerPushService serverPushService;
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final long pollInterval;
|
||||
private final int pageSize;
|
||||
|
||||
|
@ -95,6 +97,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
this.serverPushService = serverPushService;
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.i18nSupport = resourceService.getI18nSupport();
|
||||
this.pollInterval = pollInterval;
|
||||
this.pageSize = pageSize;
|
||||
|
||||
|
@ -222,8 +225,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.pageService
|
||||
.getI18nSupport()
|
||||
return this.i18nSupport
|
||||
.formatDisplayTime(Utils.toDateTimeUTC(event.getClientTime()));
|
||||
}
|
||||
|
||||
|
@ -232,8 +234,7 @@ public class MonitoringClientConnection implements TemplateComposer {
|
|||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.pageService
|
||||
.getI18nSupport()
|
||||
return this.i18nSupport
|
||||
.formatDisplayTime(Utils.toDateTimeUTC(event.getServerTime()));
|
||||
}
|
||||
|
||||
|
|
|
@ -202,7 +202,7 @@ public class QuizDiscoveryList implements TemplateComposer {
|
|||
return action
|
||||
.withEntityKey(action.getSingleSelection())
|
||||
.withParentEntityKey(new EntityKey(selectedROWData.lmsSetupId, EntityType.LMS_SETUP))
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZZ_DATA, "true");
|
||||
.withAttribute(AttributeKeys.IMPORT_FROM_QUIZ_DATA, "true");
|
||||
}
|
||||
|
||||
private PageAction showDetails(final PageAction action, final QuizData quizData) {
|
||||
|
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageContext;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.TemplateComposer;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class SebClientLogs implements TemplateComposer {
|
||||
|
||||
public SebClientLogs() {
|
||||
// TODO Auto-generated constructor stub
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -47,6 +47,8 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory;
|
|||
public class UserAccountList implements TemplateComposer {
|
||||
|
||||
// localized text keys
|
||||
private static final LocTextKey EMPTY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.useraccount.list.empty");
|
||||
private static final LocTextKey INSTITUTION_TEXT_KEY =
|
||||
new LocTextKey("sebserver.useraccount.list.column.institution");
|
||||
private static final LocTextKey EMPTY_SELECTION_TEXT_KEY =
|
||||
|
@ -121,9 +123,9 @@ public class UserAccountList implements TemplateComposer {
|
|||
// table
|
||||
final EntityTable<UserInfo> table = this.pageService.entityTableBuilder(
|
||||
restService.getRestCall(GetUserAccountPage.class))
|
||||
|
||||
.withEmptyMessage(new LocTextKey("sebserver.useraccount.list.empty"))
|
||||
.withEmptyMessage(EMPTY_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
|
||||
.withColumnIf(
|
||||
isSEBAdmin,
|
||||
() -> new ColumnDefinition<>(
|
||||
|
@ -132,6 +134,7 @@ public class UserAccountList implements TemplateComposer {
|
|||
userInstitutionNameFunction(this.resourceService))
|
||||
.withFilter(this.institutionFilter)
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.USER.ATTR_NAME,
|
||||
NAME_TEXT_KEY,
|
||||
|
@ -139,6 +142,7 @@ public class UserAccountList implements TemplateComposer {
|
|||
.withFilter(this.nameFilter)
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.USER.ATTR_USERNAME,
|
||||
USER_NAME_TEXT_KEY,
|
||||
|
@ -146,6 +150,7 @@ public class UserAccountList implements TemplateComposer {
|
|||
.withFilter(this.usernameFilter)
|
||||
.sortable()
|
||||
.widthProportion(2))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.USER.ATTR_EMAIL,
|
||||
MAIL_TEXT_KEY,
|
||||
|
@ -153,6 +158,7 @@ public class UserAccountList implements TemplateComposer {
|
|||
.withFilter(this.mailFilter)
|
||||
.sortable()
|
||||
.widthProportion(3))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.USER.ATTR_LANGUAGE,
|
||||
LANG_TEXT_KEY,
|
||||
|
@ -161,6 +167,7 @@ public class UserAccountList implements TemplateComposer {
|
|||
.localized()
|
||||
.sortable()
|
||||
.widthProportion(1))
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.USER.ATTR_ACTIVE,
|
||||
ACTIVE_TEXT_KEY,
|
||||
|
@ -168,6 +175,7 @@ public class UserAccountList implements TemplateComposer {
|
|||
.sortable()
|
||||
.withFilter(this.activityFilter)
|
||||
.widthProportion(1))
|
||||
|
||||
.withDefaultAction(actionBuilder
|
||||
.newAction(ActionDefinition.USER_ACCOUNT_VIEW_FROM_LIST)
|
||||
.create())
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.content;
|
||||
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Domain;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
||||
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.PageService.PageActionBuilder;
|
||||
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.webservice.api.RestService;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.userlogs.GetUserLogPage;
|
||||
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 UserActivityLogs implements TemplateComposer {
|
||||
|
||||
private static final LocTextKey DETAILS_TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.userlogs.details.title");
|
||||
private static final LocTextKey TITLE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.userlogs.list.title");
|
||||
private static final LocTextKey EMPTY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.userlogs.list.empty");
|
||||
private static final LocTextKey USER_TEXT_KEY =
|
||||
new LocTextKey("sebserver.userlogs.list.column.user");
|
||||
private static final LocTextKey DATE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.userlogs.list.column.dateTime");
|
||||
private static final LocTextKey ACTIVITY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.userlogs.list.column.activityType");
|
||||
private static final LocTextKey ENTITY_TEXT_KEY =
|
||||
new LocTextKey("sebserver.userlogs.list.column.entityType");
|
||||
private static final LocTextKey MESSAGE_TEXT_KEY =
|
||||
new LocTextKey("sebserver.userlogs.list.column.message");
|
||||
private final static LocTextKey EMPTY_SELECTION_TEXT =
|
||||
new LocTextKey("sebserver.userlogs.info.pleaseSelect");
|
||||
|
||||
private final TableFilterAttribute userFilter;
|
||||
private final TableFilterAttribute activityFilter;
|
||||
private final TableFilterAttribute entityFilter;
|
||||
|
||||
private final PageService pageService;
|
||||
private final ResourceService resourceService;
|
||||
private final I18nSupport i18nSupport;
|
||||
private final WidgetFactory widgetFactory;
|
||||
private final int pageSize;
|
||||
|
||||
public UserActivityLogs(
|
||||
final PageService pageService,
|
||||
final ResourceService resourceService,
|
||||
@Value("${sebserver.gui.list.page.size:20}") final Integer pageSize) {
|
||||
|
||||
this.pageService = pageService;
|
||||
this.resourceService = resourceService;
|
||||
this.i18nSupport = resourceService.getI18nSupport();
|
||||
this.widgetFactory = pageService.getWidgetFactory();
|
||||
this.pageSize = pageSize;
|
||||
|
||||
this.userFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
UserActivityLog.FILTER_ATTR_USER,
|
||||
this.resourceService::userResources);
|
||||
|
||||
this.activityFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
UserActivityLog.FILTER_ATTR_ACTIVITY_TYPES,
|
||||
this.resourceService::userActivityTypeResources);
|
||||
|
||||
this.entityFilter = new TableFilterAttribute(
|
||||
CriteriaType.SINGLE_SELECTION,
|
||||
UserActivityLog.FILTER_ATTR_ENTITY_TYPES,
|
||||
this.resourceService::entityTypeResources);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void compose(final PageContext pageContext) {
|
||||
final WidgetFactory widgetFactory = this.pageService.getWidgetFactory();
|
||||
final RestService restService = this.resourceService.getRestService();
|
||||
// content page layout with title
|
||||
final Composite content = widgetFactory.defaultPageLayout(
|
||||
pageContext.getParent(),
|
||||
TITLE_TEXT_KEY);
|
||||
|
||||
final PageActionBuilder actionBuilder = this.pageService.pageActionBuilder(
|
||||
pageContext
|
||||
.clearEntityKeys()
|
||||
.clearAttributes());
|
||||
|
||||
// table
|
||||
final EntityTable<UserActivityLog> table = this.pageService.entityTableBuilder(
|
||||
restService.getRestCall(GetUserLogPage.class))
|
||||
.withEmptyMessage(EMPTY_TEXT_KEY)
|
||||
.withPaging(this.pageSize)
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
UserActivityLog.ATTR_USER_NAME,
|
||||
USER_TEXT_KEY,
|
||||
UserActivityLog::getUsername)
|
||||
.withFilter(this.userFilter))
|
||||
|
||||
.withColumn(new ColumnDefinition<UserActivityLog>(
|
||||
Domain.USER_ACTIVITY_LOG.ATTR_ACTIVITY_TYPE,
|
||||
ACTIVITY_TEXT_KEY,
|
||||
this.resourceService::getUserActivityTypeName)
|
||||
.withFilter(this.activityFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<UserActivityLog>(
|
||||
Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_ID,
|
||||
ENTITY_TEXT_KEY,
|
||||
this.resourceService::getEntityTypeName)
|
||||
.withFilter(this.entityFilter)
|
||||
.sortable())
|
||||
|
||||
.withColumn(new ColumnDefinition<>(
|
||||
Domain.USER_ACTIVITY_LOG.ATTR_TIMESTAMP,
|
||||
DATE_TEXT_KEY,
|
||||
this::getLogTime)
|
||||
.withFilter(new TableFilterAttribute(
|
||||
CriteriaType.DATE_RANGE,
|
||||
UserActivityLog.FILTER_ATTR_FROM_TO,
|
||||
Utils.toDateTimeUTC(Utils.getMillisecondsNow())
|
||||
.minusYears(1)
|
||||
.toString()))
|
||||
.sortable())
|
||||
|
||||
.withDefaultAction(t -> actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS)
|
||||
.withExec(action -> this.showDetails(action, t.getSelectedROWData()))
|
||||
.noEventPropagation()
|
||||
.create())
|
||||
|
||||
.compose(content);
|
||||
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_SHOW_DETAILS)
|
||||
.withSelect(
|
||||
table::getSelection,
|
||||
action -> this.showDetails(action, table.getSelectedROWData()),
|
||||
EMPTY_SELECTION_TEXT)
|
||||
.noEventPropagation()
|
||||
.publishIf(table::hasAnyContent);
|
||||
|
||||
}
|
||||
|
||||
private final String getLogTime(final UserActivityLog log) {
|
||||
if (log == null || log.timestamp == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
|
||||
return this.i18nSupport
|
||||
.formatDisplayDateTime(Utils.toDateTimeUTC(log.timestamp));
|
||||
}
|
||||
|
||||
private PageAction showDetails(final PageAction action, final UserActivityLog userActivityLog) {
|
||||
action.getSingleSelection();
|
||||
|
||||
final ModalInputDialog<Void> dialog = new ModalInputDialog<>(
|
||||
action.pageContext().getParent().getShell(),
|
||||
this.widgetFactory);
|
||||
|
||||
dialog.open(
|
||||
DETAILS_TITLE_TEXT_KEY,
|
||||
action.pageContext(),
|
||||
pc -> createDetailsForm(userActivityLog, pc));
|
||||
|
||||
return action;
|
||||
}
|
||||
|
||||
private void createDetailsForm(final UserActivityLog userActivityLog, final PageContext pc) {
|
||||
this.pageService.formBuilder(pc, 3)
|
||||
.withEmptyCellSeparation(false)
|
||||
.readonly(true)
|
||||
.addField(FormBuilder.text(
|
||||
UserActivityLog.ATTR_USER_NAME,
|
||||
USER_TEXT_KEY,
|
||||
String.valueOf(userActivityLog.getUsername())))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.USER_ACTIVITY_LOG.ATTR_ACTIVITY_TYPE,
|
||||
ACTIVITY_TEXT_KEY,
|
||||
this.resourceService.getUserActivityTypeName(userActivityLog)))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.USER_ACTIVITY_LOG.ATTR_ENTITY_ID,
|
||||
ENTITY_TEXT_KEY,
|
||||
this.resourceService.getEntityTypeName(userActivityLog)))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.USER_ACTIVITY_LOG.ATTR_TIMESTAMP,
|
||||
DATE_TEXT_KEY,
|
||||
this.widgetFactory.getI18nSupport()
|
||||
.formatDisplayDateTime(Utils.toDateTimeUTC(userActivityLog.timestamp))))
|
||||
.addField(FormBuilder.text(
|
||||
Domain.USER_ACTIVITY_LOG.ATTR_MESSAGE,
|
||||
MESSAGE_TEXT_KEY,
|
||||
String.valueOf(userActivityLog.message).replace(",", ",\n"))
|
||||
.asArea())
|
||||
.build();
|
||||
this.widgetFactory.labelSeparator(pc.getParent());
|
||||
}
|
||||
|
||||
}
|
|
@ -23,6 +23,8 @@ public enum ActionCategory {
|
|||
SEB_EXAM_CONFIG_LIST(new LocTextKey("sebserver.examconfig.list.actions"), 1),
|
||||
RUNNING_EXAM_LIST(new LocTextKey("sebserver.monitoring.exam.list.actions"), 1),
|
||||
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"), 100),
|
||||
;
|
||||
|
||||
|
|
|
@ -242,6 +242,10 @@ public enum ActionDefinition {
|
|||
ImageIcon.DELETE,
|
||||
PageStateDefinition.EXAM_VIEW,
|
||||
ActionCategory.EXAM_CONFIG_MAPPING_LIST),
|
||||
EXAM_CONFIGURATION_GET_CONFIG_KEY(
|
||||
new LocTextKey("sebserver.examconfig.action.get-config-key"),
|
||||
ImageIcon.SECURE,
|
||||
ActionCategory.EXAM_CONFIG_MAPPING_LIST),
|
||||
EXAM_CONFIGURATION_SAVE(
|
||||
new LocTextKey("sebserver.exam.configuration.action.save"),
|
||||
ImageIcon.SAVE,
|
||||
|
@ -416,6 +420,22 @@ public enum ActionDefinition {
|
|||
PageStateDefinition.MONITORING_RUNNING_EXAM,
|
||||
ActionCategory.VARIA),
|
||||
|
||||
LOGS_USER_ACTIVITY_LIST(
|
||||
new LocTextKey("sebserver.logs.activity.userlogs"),
|
||||
PageStateDefinition.USER_ACTIVITY_LOGS),
|
||||
LOGS_USER_ACTIVITY_SHOW_DETAILS(
|
||||
new LocTextKey("sebserver.logs.activity.userlogs.details"),
|
||||
ImageIcon.SHOW,
|
||||
ActionCategory.LOGS_USER_ACTIVITY_LIST),
|
||||
|
||||
LOGS_SEB_CLIENT(
|
||||
new LocTextKey("sebserver.logs.activity.seblogs"),
|
||||
PageStateDefinition.SEB_CLIENT_LOGS),
|
||||
LOGS_SEB_CLIENT_SHOW_DETAILS(
|
||||
new LocTextKey("sebserver.logs.activity.seblogs.details"),
|
||||
ImageIcon.SHOW,
|
||||
ActionCategory.LOGS_SEB_CLIENT_LIST),
|
||||
|
||||
;
|
||||
|
||||
public final LocTextKey title;
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.content.activity;
|
||||
|
||||
import org.eclipse.rap.rwt.RWT;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Event;
|
||||
|
@ -176,34 +177,46 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
final boolean examConfigRead = this.currentUser.hasInstitutionalPrivilege(
|
||||
PrivilegeType.READ,
|
||||
EntityType.CONFIGURATION_NODE);
|
||||
if (clientConfigRead || examConfigRead) {
|
||||
final TreeItem sebConfigs = this.widgetFactory.treeItemLocalized(
|
||||
|
||||
TreeItem sebConfigs = null;
|
||||
if (clientConfigRead && examConfigRead) {
|
||||
sebConfigs = this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.SEB_CONFIGURATION.displayName);
|
||||
sebConfigs.setData(RWT.CUSTOM_VARIANT, CustomVariant.ACTIVITY_TREE_SECTION.key);
|
||||
|
||||
// SEB Client Config
|
||||
if (clientConfigRead) {
|
||||
final TreeItem clientConfig = this.widgetFactory.treeItemLocalized(
|
||||
sebConfigs,
|
||||
ActivityDefinition.SEB_CLIENT_CONFIG.displayName);
|
||||
injectActivitySelection(
|
||||
clientConfig,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_LIST)
|
||||
.create());
|
||||
}
|
||||
}
|
||||
|
||||
// SEB Exam Config
|
||||
if (examConfigRead) {
|
||||
final TreeItem examConfig = this.widgetFactory.treeItemLocalized(
|
||||
sebConfigs,
|
||||
ActivityDefinition.SEB_EXAM_CONFIG.displayName);
|
||||
injectActivitySelection(
|
||||
examConfig,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_LIST)
|
||||
.create());
|
||||
}
|
||||
// SEB Client Config
|
||||
if (clientConfigRead) {
|
||||
final TreeItem clientConfig = (sebConfigs != null)
|
||||
? this.widgetFactory.treeItemLocalized(
|
||||
sebConfigs,
|
||||
ActivityDefinition.SEB_CLIENT_CONFIG.displayName)
|
||||
: this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.SEB_CLIENT_CONFIG.displayName);
|
||||
injectActivitySelection(
|
||||
clientConfig,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.SEB_CLIENT_CONFIG_LIST)
|
||||
.create());
|
||||
}
|
||||
|
||||
// SEB Exam Config
|
||||
if (examConfigRead) {
|
||||
final TreeItem examConfig = (sebConfigs != null)
|
||||
? this.widgetFactory.treeItemLocalized(
|
||||
sebConfigs,
|
||||
ActivityDefinition.SEB_EXAM_CONFIG.displayName)
|
||||
: this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.SEB_EXAM_CONFIG.displayName);
|
||||
injectActivitySelection(
|
||||
examConfig,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.SEB_EXAM_CONFIG_LIST)
|
||||
.create());
|
||||
}
|
||||
|
||||
// Monitoring exams
|
||||
|
@ -218,10 +231,68 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
.create());
|
||||
}
|
||||
|
||||
// Logs
|
||||
final boolean viewUserActivityLogs = this.currentUser.hasInstitutionalPrivilege(
|
||||
PrivilegeType.READ,
|
||||
EntityType.USER_ACTIVITY_LOG);
|
||||
final boolean viewSebClientLogs = false;
|
||||
// this.currentUser.hasInstitutionalPrivilege(
|
||||
// PrivilegeType.READ,
|
||||
// EntityType.EXAM);
|
||||
|
||||
TreeItem logRoot = null;
|
||||
if (viewUserActivityLogs && viewSebClientLogs) {
|
||||
logRoot = this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.LOGS.displayName);
|
||||
logRoot.setData(RWT.CUSTOM_VARIANT, CustomVariant.ACTIVITY_TREE_SECTION.key);
|
||||
}
|
||||
|
||||
// User Activity Logs
|
||||
if (viewUserActivityLogs) {
|
||||
final TreeItem activityLogs = (logRoot != null)
|
||||
? this.widgetFactory.treeItemLocalized(
|
||||
logRoot,
|
||||
ActivityDefinition.USER_ACTIVITY_LOGS.displayName)
|
||||
: this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.USER_ACTIVITY_LOGS.displayName);
|
||||
injectActivitySelection(
|
||||
activityLogs,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_USER_ACTIVITY_LIST)
|
||||
.create());
|
||||
}
|
||||
|
||||
// SEB Client Logs
|
||||
if (viewSebClientLogs) {
|
||||
final TreeItem sebLogs = (logRoot != null)
|
||||
? this.widgetFactory.treeItemLocalized(
|
||||
logRoot,
|
||||
ActivityDefinition.SEB_CLIENT_LOGS.displayName)
|
||||
: this.widgetFactory.treeItemLocalized(
|
||||
navigation,
|
||||
ActivityDefinition.SEB_CLIENT_LOGS.displayName);
|
||||
injectActivitySelection(
|
||||
sebLogs,
|
||||
actionBuilder
|
||||
.newAction(ActionDefinition.LOGS_SEB_CLIENT)
|
||||
.create());
|
||||
}
|
||||
|
||||
// TODO other activities
|
||||
|
||||
// register page listener and initialize navigation data
|
||||
navigation.addListener(SWT.Selection, event -> handleSelection(pageContext, event));
|
||||
navigation.addListener(SWT.Expand, event -> {
|
||||
final Tree tree = (Tree) event.widget;
|
||||
final TreeItem treeItem = (TreeItem) event.item;
|
||||
final TreeItem[] selection = tree.getSelection();
|
||||
if (selection != null && selection.length >= 1 && selection[0].getParentItem() == treeItem) {
|
||||
tree.setSelection(selection[0]);
|
||||
//tree.select(selection[0]);
|
||||
}
|
||||
});
|
||||
navigation.setData(
|
||||
PageEventListener.LISTENER_ATTRIBUTE_KEY,
|
||||
new ActivitiesActionEventListener(navigation));
|
||||
|
@ -247,8 +318,16 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
// if there is no form action associated with the treeItem and the treeItem has sub items, toggle the item state
|
||||
if (action == null) {
|
||||
if (treeItem.getItemCount() > 0) {
|
||||
treeItem.setExpanded(!treeItem.getExpanded());
|
||||
treeItem.setExpanded(true);
|
||||
final PageState currentState = this.pageService.getCurrentState();
|
||||
final TreeItem currentSelection = findItemByActionDefinition(tree.getItems(), currentState);
|
||||
if (currentSelection != null) {
|
||||
tree.select(currentSelection);
|
||||
} else {
|
||||
tree.deselectAll();
|
||||
}
|
||||
}
|
||||
tree.layout();
|
||||
return;
|
||||
}
|
||||
this.pageService.executePageAction(
|
||||
|
@ -279,6 +358,12 @@ public class ActivitiesPane implements TemplateComposer {
|
|||
for (final TreeItem item : items) {
|
||||
final PageAction action = getActivitySelection(item);
|
||||
if (action == null) {
|
||||
if (item.getItemCount() > 0) {
|
||||
final TreeItem found = findItemByActionDefinition(item.getItems(), activity, modelId);
|
||||
if (found != null) {
|
||||
return found;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,7 +20,10 @@ public enum ActivityDefinition implements Activity {
|
|||
SEB_CONFIGURATION(new LocTextKey("sebserver.sebconfig.activity.name")),
|
||||
SEB_CLIENT_CONFIG(new LocTextKey("sebserver.clientconfig.action.list")),
|
||||
SEB_EXAM_CONFIG(new LocTextKey("sebserver.examconfig.action.list")),
|
||||
MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list"));
|
||||
MONITORING_EXAMS(new LocTextKey("sebserver.monitoring.action.list")),
|
||||
LOGS(new LocTextKey("sebserver.logs.activity.main")),
|
||||
USER_ACTIVITY_LOGS(new LocTextKey("sebserver.logs.activity.userlogs")),
|
||||
SEB_CLIENT_LOGS(new LocTextKey("sebserver.logs.activity.seblogs"));
|
||||
|
||||
public final LocTextKey displayName;
|
||||
|
||||
|
|
|
@ -22,12 +22,14 @@ import ch.ethz.seb.sebserver.gui.content.MonitoringRunningExamList;
|
|||
import ch.ethz.seb.sebserver.gui.content.QuizDiscoveryList;
|
||||
import ch.ethz.seb.sebserver.gui.content.SebClientConfigForm;
|
||||
import ch.ethz.seb.sebserver.gui.content.SebClientConfigList;
|
||||
import ch.ethz.seb.sebserver.gui.content.SebClientLogs;
|
||||
import ch.ethz.seb.sebserver.gui.content.SebExamConfigList;
|
||||
import ch.ethz.seb.sebserver.gui.content.SebExamConfigPropForm;
|
||||
import ch.ethz.seb.sebserver.gui.content.SebExamConfigSettingsForm;
|
||||
import ch.ethz.seb.sebserver.gui.content.UserAccountChangePasswordForm;
|
||||
import ch.ethz.seb.sebserver.gui.content.UserAccountForm;
|
||||
import ch.ethz.seb.sebserver.gui.content.UserAccountList;
|
||||
import ch.ethz.seb.sebserver.gui.content.UserActivityLogs;
|
||||
import ch.ethz.seb.sebserver.gui.content.action.ActionPane;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.Activity;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageState;
|
||||
|
@ -67,7 +69,10 @@ public enum PageStateDefinition implements PageState {
|
|||
|
||||
MONITORING_RUNNING_EXAM_LIST(Type.LIST_VIEW, MonitoringRunningExamList.class, ActivityDefinition.MONITORING_EXAMS),
|
||||
MONITORING_RUNNING_EXAM(Type.FORM_VIEW, MonitoringRunningExam.class, ActivityDefinition.MONITORING_EXAMS),
|
||||
MONITORING_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.MONITORING_EXAMS)
|
||||
MONITORING_CLIENT_CONNECTION(Type.FORM_VIEW, MonitoringClientConnection.class, ActivityDefinition.MONITORING_EXAMS),
|
||||
|
||||
USER_ACTIVITY_LOGS(Type.LIST_VIEW, UserActivityLogs.class, ActivityDefinition.USER_ACTIVITY_LOGS),
|
||||
SEB_CLIENT_LOGS(Type.LIST_VIEW, SebClientLogs.class, ActivityDefinition.SEB_CLIENT_LOGS)
|
||||
|
||||
;
|
||||
|
||||
|
|
|
@ -82,6 +82,7 @@ public final class Form implements FormBinding {
|
|||
.forEach(entry -> {
|
||||
entry.getValue()
|
||||
.stream()
|
||||
.filter(Form::valueApplicationFilter)
|
||||
.forEach(ffa -> {
|
||||
if (ffa.listValue) {
|
||||
appendFormUrlEncodedValue(
|
||||
|
@ -118,8 +119,8 @@ public final class Form implements FormBinding {
|
|||
return !this.formFields.isEmpty();
|
||||
}
|
||||
|
||||
Form putField(final String name, final Label label, final Label field) {
|
||||
this.formFields.add(name, createAccessor(label, field));
|
||||
Form putReadonlyField(final String name, final Label label, final Text field) {
|
||||
this.formFields.add(name, createReadonlyAccessor(label, field));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -225,14 +226,18 @@ public final class Form implements FormBinding {
|
|||
for (final Map.Entry<String, List<FormFieldAccessor>> entry : this.formFields.entrySet()) {
|
||||
entry.getValue()
|
||||
.stream()
|
||||
.filter(ffa -> StringUtils.isNotBlank(ffa.getStringValue()))
|
||||
.filter(Form::valueApplicationFilter)
|
||||
.forEach(ffa -> ffa.putJsonValue(entry.getKey(), this.objectRoot));
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean valueApplicationFilter(final FormFieldAccessor ffa) {
|
||||
return ffa.getStringValue() != null;
|
||||
}
|
||||
|
||||
// following are FormFieldAccessor implementations for all field types
|
||||
//@formatter:off
|
||||
private FormFieldAccessor createAccessor(final Label label, final Label field) {
|
||||
private FormFieldAccessor createReadonlyAccessor(final Label label, final Text field) {
|
||||
return new FormFieldAccessor(label, field, null) {
|
||||
@Override public String getStringValue() { return null; }
|
||||
@Override public void setStringValue(final String value) { field.setText( (value == null) ? StringUtils.EMPTY : value); }
|
||||
|
@ -241,7 +246,7 @@ public final class Form implements FormBinding {
|
|||
private FormFieldAccessor createAccessor(final Label label, final Text text, final Label errorLabel) {
|
||||
return new FormFieldAccessor(label, text, errorLabel) {
|
||||
@Override public String getStringValue() {return text.getText();}
|
||||
@Override public void setStringValue(final String value) { text.setText( (value == null) ? StringUtils.EMPTY : value); }
|
||||
@Override public void setStringValue(final String value) {text.setText(value);}
|
||||
};
|
||||
}
|
||||
private FormFieldAccessor createAccessor(final Label label, final Selection selection, final Label errorLabel) {
|
||||
|
@ -381,7 +386,7 @@ public final class Form implements FormBinding {
|
|||
this.jsonValueAdapter = jsonValueAdapter;
|
||||
} else {
|
||||
this.jsonValueAdapter = (tuple, jsonObject) -> {
|
||||
if (StringUtils.isNotBlank(tuple._2)) {
|
||||
if (tuple._2 != null) {
|
||||
jsonObject.put(tuple._1, tuple._2);
|
||||
}
|
||||
};
|
||||
|
@ -448,7 +453,7 @@ public final class Form implements FormBinding {
|
|||
gridLayout.marginRight = 0;
|
||||
fieldGrid.setLayout(gridLayout);
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
gridData.horizontalSpan = hspan;
|
||||
fieldGrid.setLayoutData(gridData);
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ public class FormBuilder {
|
|||
|
||||
this.formParent = this.widgetFactory
|
||||
.formGrid(pageContext.getParent(), rows);
|
||||
this.formParent.setData("TEST");
|
||||
}
|
||||
|
||||
public FormBuilder readonly(final boolean readonly) {
|
||||
|
@ -271,7 +272,7 @@ public class FormBuilder {
|
|||
final GridData gridData = new GridData(
|
||||
(centered) ? SWT.FILL : SWT.FILL,
|
||||
(centered) ? SWT.CENTER : SWT.TOP,
|
||||
true, true,
|
||||
true, false,
|
||||
hspan, 1);
|
||||
|
||||
if (centered) {
|
||||
|
@ -279,7 +280,7 @@ public class FormBuilder {
|
|||
label.setData(RWT.CUSTOM_VARIANT, CustomVariant.FORM_CENTER.key);
|
||||
}
|
||||
|
||||
gridData.heightHint = FORM_ROW_HEIGHT;
|
||||
// gridData.heightHint = FORM_ROW_HEIGHT;
|
||||
label.setLayoutData(gridData);
|
||||
return label;
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ import org.eclipse.swt.layout.GridLayout;
|
|||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Control;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Tuple;
|
||||
|
@ -100,7 +101,7 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
|
|||
if (this.type == Type.MULTI || this.type == Type.MULTI_COMBO) {
|
||||
final Composite composite = new Composite(builder.formParent, SWT.NONE);
|
||||
final GridLayout gridLayout = new GridLayout(1, true);
|
||||
gridLayout.verticalSpacing = 0;
|
||||
gridLayout.verticalSpacing = 5;
|
||||
gridLayout.horizontalSpacing = 0;
|
||||
gridLayout.marginLeft = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
|
@ -120,7 +121,7 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
|
|||
.forEach(v -> buildReadonlyLabel(composite, v, 0));
|
||||
}
|
||||
} else {
|
||||
builder.form.putField(
|
||||
builder.form.putReadonlyField(
|
||||
this.name,
|
||||
lab,
|
||||
buildReadonlyLabel(builder.formParent, this.value, this.spanInput));
|
||||
|
@ -128,9 +129,9 @@ public final class SelectionFieldBuilder extends FieldBuilder<String> {
|
|||
}
|
||||
}
|
||||
|
||||
private Label buildReadonlyLabel(final Composite composite, final String valueKey, final int hspan) {
|
||||
final Label label = new Label(composite, SWT.NONE);
|
||||
final GridData gridData = new GridData(SWT.LEFT, SWT.FILL, true, true, hspan, 1);
|
||||
private Text buildReadonlyLabel(final Composite composite, final String valueKey, final int hspan) {
|
||||
final Text label = new Text(composite, SWT.READ_ONLY);
|
||||
final GridData gridData = new GridData(SWT.LEFT, SWT.TOP, true, false, hspan, 1);
|
||||
gridData.verticalIndent = 0;
|
||||
gridData.horizontalIndent = 0;
|
||||
label.setLayoutData(gridData);
|
||||
|
|
|
@ -8,12 +8,14 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.gui.form;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.eclipse.swt.SWT;
|
||||
import org.eclipse.swt.layout.GridData;
|
||||
import org.eclipse.swt.widgets.Composite;
|
||||
import org.eclipse.swt.widgets.Label;
|
||||
import org.eclipse.swt.widgets.Text;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
|
||||
public final class TextFieldBuilder extends FieldBuilder<String> {
|
||||
|
@ -47,37 +49,39 @@ public final class TextFieldBuilder extends FieldBuilder<String> {
|
|||
return;
|
||||
}
|
||||
|
||||
final boolean readonly = builder.readonly || this.readonly;
|
||||
final Label lab = builder.labelLocalized(
|
||||
builder.formParent,
|
||||
this.label,
|
||||
this.defaultLabel,
|
||||
this.spanLabel);
|
||||
|
||||
if (builder.readonly || this.readonly) {
|
||||
builder.form.putField(this.name, lab,
|
||||
builder.valueLabel(builder.formParent, this.value, this.spanInput, this.centeredInput));
|
||||
builder.setFieldVisible(this.visible, this.name);
|
||||
final Composite fieldGrid = Form.createFieldGrid(builder.formParent, this.spanInput);
|
||||
final Text textInput = (this.isNumber)
|
||||
? builder.widgetFactory.numberInput(fieldGrid, null)
|
||||
: (this.isArea)
|
||||
? builder.widgetFactory.textAreaInput(fieldGrid)
|
||||
: builder.widgetFactory.textInput(fieldGrid, this.isPassword);
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||
if (this.isArea && !readonly) {
|
||||
gridData.heightHint = 50;
|
||||
}
|
||||
textInput.setLayoutData(gridData);
|
||||
if (StringUtils.isNoneBlank(this.value)) {
|
||||
textInput.setText(this.value);
|
||||
} else if (readonly) {
|
||||
textInput.setText(Constants.EMPTY_NOTE);
|
||||
}
|
||||
|
||||
if (readonly) {
|
||||
textInput.setEditable(false);
|
||||
builder.form.putReadonlyField(this.name, lab, textInput);
|
||||
} else {
|
||||
|
||||
final Composite fieldGrid = Form.createFieldGrid(builder.formParent, this.spanInput);
|
||||
final Text textInput = (this.isNumber)
|
||||
? builder.widgetFactory.numberInput(fieldGrid, null)
|
||||
: (this.isArea)
|
||||
? builder.widgetFactory.textAreaInput(fieldGrid)
|
||||
: builder.widgetFactory.textInput(fieldGrid, this.isPassword);
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||
if (this.isArea) {
|
||||
gridData.heightHint = 50;
|
||||
}
|
||||
textInput.setLayoutData(gridData);
|
||||
if (this.value != null) {
|
||||
textInput.setText(this.value);
|
||||
}
|
||||
|
||||
final Label errorLabel = Form.createErrorLabel(fieldGrid);
|
||||
builder.form.putField(this.name, lab, textInput, errorLabel);
|
||||
builder.setFieldVisible(this.visible, this.name);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -49,6 +49,7 @@ public class ThresholdListBuilder extends FieldBuilder<Collection<Threshold>> {
|
|||
final Composite fieldGrid = Form.createFieldGrid(builder.formParent, this.spanInput);
|
||||
final ThresholdList thresholdList = builder.widgetFactory.thresholdList(
|
||||
fieldGrid,
|
||||
fieldGrid.getParent().getParent(),
|
||||
this.value);
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, false);
|
||||
|
|
|
@ -11,6 +11,8 @@ package ch.ethz.seb.sebserver.gui.service;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
|
@ -24,6 +26,7 @@ import org.springframework.context.annotation.Lazy;
|
|||
import org.springframework.stereotype.Service;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.Constants;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Entity;
|
||||
import ch.ethz.seb.sebserver.gbl.model.EntityName;
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Exam;
|
||||
|
@ -35,7 +38,9 @@ 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.session.ClientEvent.EventType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserRole;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
|
@ -56,12 +61,28 @@ import ch.ethz.seb.sebserver.gui.service.remote.webservice.auth.CurrentUser;
|
|||
* combo-box content. */
|
||||
public class ResourceService {
|
||||
|
||||
public static final Comparator<Tuple<String>> RESOURCE_COMPARATOR = (t1, t2) -> t1._2.compareTo(t2._2);
|
||||
|
||||
public static final EnumSet<EntityType> ENTITY_TYPE_EXCLUDE_MAP = EnumSet.of(
|
||||
EntityType.ADDITIONAL_ATTRIBUTES,
|
||||
EntityType.CLIENT_CONNECTION,
|
||||
EntityType.CLIENT_EVENT,
|
||||
EntityType.CONFIGURATION_ATTRIBUTE,
|
||||
EntityType.CONFIGURATION_VALUE,
|
||||
EntityType.CONFIGURATION,
|
||||
EntityType.ORIENTATION,
|
||||
EntityType.USER_ACTIVITY_LOG,
|
||||
EntityType.USER_ROLE,
|
||||
EntityType.WEBSERVICE_SERVER_INFO);
|
||||
|
||||
public static final String EXAMCONFIG_STATUS_PREFIX = "sebserver.examconfig.status.";
|
||||
public static final String EXAM_TYPE_PREFIX = "sebserver.exam.type.";
|
||||
public static final String USERACCOUNT_ROLE_PREFIX = "sebserver.useraccount.role.";
|
||||
public static final String EXAM_INDICATOR_TYPE_PREFIX = "sebserver.exam.indicator.type.";
|
||||
public static final String LMSSETUP_TYPE_PREFIX = "sebserver.lmssetup.type.";
|
||||
public static final String CLIENT_EVENT_TYPE_PREFIX = "sebserver.monitoring.exam.connection.event.type.";
|
||||
public static final String USER_ACTIVITY_TYPE_PREFIX = "sebserver.overall.types.activityType.";
|
||||
public static final String ENTITY_TYPE_PREFIX = "sebserver.overall.types.entityType.";
|
||||
public static final LocTextKey ACTIVE_TEXT_KEY = new LocTextKey("sebserver.overall.status.active");
|
||||
public static final LocTextKey INACTIVE_TEXT_KEY = new LocTextKey("sebserver.overall.status.inactive");
|
||||
|
||||
|
@ -110,6 +131,7 @@ public class ResourceService {
|
|||
.map(lmsType -> new Tuple<>(
|
||||
lmsType.name(),
|
||||
this.i18nSupport.getText(LMSSETUP_TYPE_PREFIX + lmsType.name(), lmsType.name())))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -119,6 +141,7 @@ public class ResourceService {
|
|||
.map(eventType -> new Tuple<>(
|
||||
eventType.name(),
|
||||
getEventTypeName(eventType)))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -135,6 +158,7 @@ public class ResourceService {
|
|||
.map(type -> new Tuple<>(
|
||||
type.name(),
|
||||
this.i18nSupport.getText(EXAM_INDICATOR_TYPE_PREFIX + type.name(), type.name())))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -143,6 +167,7 @@ public class ResourceService {
|
|||
.getOr(Collections.emptyList())
|
||||
.stream()
|
||||
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -152,6 +177,7 @@ public class ResourceService {
|
|||
.map(ur -> new Tuple<>(
|
||||
ur.name(),
|
||||
this.i18nSupport.getText(USERACCOUNT_ROLE_PREFIX + ur.name())))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -162,6 +188,7 @@ public class ResourceService {
|
|||
.getOr(Collections.emptyList())
|
||||
.stream()
|
||||
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -191,6 +218,7 @@ public class ResourceService {
|
|||
.getOr(Collections.emptyList())
|
||||
.stream()
|
||||
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -213,6 +241,45 @@ public class ResourceService {
|
|||
};
|
||||
}
|
||||
|
||||
public List<Tuple<String>> entityTypeResources() {
|
||||
return Arrays.asList(EntityType.values())
|
||||
.stream()
|
||||
.filter(type -> !ENTITY_TYPE_EXCLUDE_MAP.contains(type))
|
||||
.map(type -> new Tuple<>(type.name(), getEntityTypeName(type)))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public String getEntityTypeName(final EntityType type) {
|
||||
if (type == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
return this.i18nSupport.getText(ENTITY_TYPE_PREFIX + type.name());
|
||||
}
|
||||
|
||||
public String getEntityTypeName(final UserActivityLog userLog) {
|
||||
return getEntityTypeName(userLog.entityType);
|
||||
}
|
||||
|
||||
public List<Tuple<String>> userActivityTypeResources() {
|
||||
return Arrays.asList(UserLogActivityType.values())
|
||||
.stream()
|
||||
.map(type -> new Tuple<>(type.name(), getUserActivityTypeName(type)))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public String getUserActivityTypeName(final UserLogActivityType type) {
|
||||
if (type == null) {
|
||||
return Constants.EMPTY_NOTE;
|
||||
}
|
||||
return this.i18nSupport.getText(USER_ACTIVITY_TYPE_PREFIX + type.name());
|
||||
}
|
||||
|
||||
public String getUserActivityTypeName(final UserActivityLog userLog) {
|
||||
return getUserActivityTypeName(userLog.activityType);
|
||||
}
|
||||
|
||||
/** Get a list of language key/name tuples for all supported languages in the
|
||||
* language of the current users locale.
|
||||
*
|
||||
|
@ -225,7 +292,7 @@ public class ResourceService {
|
|||
.stream()
|
||||
.map(locale -> new Tuple<>(locale.toLanguageTag(), locale.getDisplayLanguage(currentLocale)))
|
||||
.filter(tuple -> StringUtils.isNotBlank(tuple._2))
|
||||
.sorted((t1, t2) -> t1._2.compareTo(t2._2))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -235,7 +302,7 @@ public class ResourceService {
|
|||
.getAvailableIDs()
|
||||
.stream()
|
||||
.map(id -> new Tuple<>(id, DateTimeZone.forID(id).getName(0, currentLocale) + " (" + id + ")"))
|
||||
.sorted((t1, t2) -> t1._2.compareTo(t2._2))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -246,6 +313,7 @@ public class ResourceService {
|
|||
.map(type -> new Tuple<>(
|
||||
type.name(),
|
||||
this.i18nSupport.getText(EXAM_TYPE_PREFIX + type.name())))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -255,6 +323,7 @@ public class ResourceService {
|
|||
.map(type -> new Tuple<>(
|
||||
type.name(),
|
||||
this.i18nSupport.getText(EXAMCONFIG_STATUS_PREFIX + type.name())))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -269,6 +338,7 @@ public class ResourceService {
|
|||
return selection
|
||||
.stream()
|
||||
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
@ -281,6 +351,7 @@ public class ResourceService {
|
|||
.getOr(Collections.emptyList())
|
||||
.stream()
|
||||
.map(entityName -> new Tuple<>(entityName.modelId, entityName.name))
|
||||
.sorted(RESOURCE_COMPARATOR)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
|
|
|
@ -36,12 +36,12 @@ public interface PageContext {
|
|||
public static final String ENTITY_TYPE = "ENTITY_TYPE";
|
||||
public static final String PARENT_ENTITY_TYPE = "PARENT_ENTITY_TYPE";
|
||||
|
||||
public static final String IMPORT_FROM_QUIZZ_DATA = "IMPORT_FROM_QUIZZ_DATA";
|
||||
public static final String IMPORT_FROM_QUIZ_DATA = "IMPORT_FROM_QUIZ_DATA";
|
||||
|
||||
}
|
||||
|
||||
/** Get the I18nSupport service
|
||||
*
|
||||
*
|
||||
* @return the I18nSupport service */
|
||||
I18nSupport getI18nSupport();
|
||||
|
||||
|
|
|
@ -47,7 +47,7 @@ public class ModalInputDialog<T> extends Dialog {
|
|||
final Shell parent,
|
||||
final WidgetFactory widgetFactory) {
|
||||
|
||||
super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL);
|
||||
super(parent, SWT.BORDER | SWT.TITLE | SWT.APPLICATION_MODAL | SWT.CLOSE);
|
||||
this.widgetFactory = widgetFactory;
|
||||
|
||||
}
|
||||
|
|
|
@ -138,11 +138,7 @@ public final class PageAction {
|
|||
PageAction.this.pageContext.publishPageMessage(pme);
|
||||
return Result.ofError(pme);
|
||||
} catch (final RestCallError restCallError) {
|
||||
if (restCallError.isFieldValidationError()) {
|
||||
PageAction.this.pageContext.publishPageMessage(
|
||||
new LocTextKey("sebserver.form.validation.error.title"),
|
||||
new LocTextKey("sebserver.form.validation.error.message"));
|
||||
} else {
|
||||
if (!restCallError.isFieldValidationError()) {
|
||||
log.error("Failed to execute action: {}", PageAction.this, restCallError);
|
||||
PageAction.this.pageContext.notifyError("action.error.unexpected.message", restCallError);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* Copyright (c) 2019 ETH Zürich, Educational Development and Technology (LET)
|
||||
*
|
||||
* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
*/
|
||||
|
||||
package ch.ethz.seb.sebserver.gui.service.remote.webservice.api.userlogs;
|
||||
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
||||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.EntityType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.GuiProfile;
|
||||
import ch.ethz.seb.sebserver.gui.service.remote.webservice.api.RestCall;
|
||||
|
||||
@Lazy
|
||||
@Component
|
||||
@GuiProfile
|
||||
public class GetUserLogPage extends RestCall<Page<UserActivityLog>> {
|
||||
|
||||
public GetUserLogPage() {
|
||||
super(new TypeKey<>(
|
||||
CallType.GET_PAGE,
|
||||
EntityType.USER_ACTIVITY_LOG,
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
}),
|
||||
HttpMethod.GET,
|
||||
MediaType.APPLICATION_FORM_URLENCODED,
|
||||
API.USER_ACTIVITY_LOG_ENDPOINT);
|
||||
}
|
||||
|
||||
}
|
|
@ -381,11 +381,7 @@ public class EntityTable<ROW extends Entity> {
|
|||
}
|
||||
|
||||
final GridData gridData = (GridData) this.table.getLayoutData();
|
||||
if (page.numberOfPages > 1) {
|
||||
gridData.heightHint = (this.pageSize + 1) * 27;
|
||||
} else {
|
||||
gridData.heightHint = (page.content.size() + 1) * 27;
|
||||
}
|
||||
gridData.heightHint = (page.content.size() * 25) + 40;
|
||||
|
||||
for (final ROW row : page.content) {
|
||||
final TableItem item = new TableItem(this.table, SWT.NONE);
|
||||
|
|
|
@ -36,6 +36,9 @@ import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
|
|||
|
||||
public class TableFilter<ROW extends Entity> {
|
||||
|
||||
private static final LocTextKey DATE_FROM_TEXT = new LocTextKey("sebserver.overall.date.from");
|
||||
private static final LocTextKey DATE_TO_TEXT = new LocTextKey("sebserver.overall.date.to");
|
||||
|
||||
public static enum CriteriaType {
|
||||
TEXT,
|
||||
SINGLE_SELECTION,
|
||||
|
@ -54,6 +57,8 @@ public class TableFilter<ROW extends Entity> {
|
|||
final RowLayout layout = new RowLayout(SWT.HORIZONTAL);
|
||||
layout.spacing = 5;
|
||||
layout.wrap = false;
|
||||
layout.center = false;
|
||||
layout.fill = true;
|
||||
this.composite.setLayout(layout);
|
||||
|
||||
// TODO just for debugging, remove when tested
|
||||
|
@ -135,32 +140,46 @@ public class TableFilter<ROW extends Entity> {
|
|||
}
|
||||
|
||||
private void addActions() {
|
||||
this.entityTable.widgetFactory.imageButton(
|
||||
final Composite inner = new Composite(this.composite, SWT.NONE);
|
||||
final GridLayout gridLayout = new GridLayout(2, true);
|
||||
gridLayout.horizontalSpacing = 5;
|
||||
gridLayout.verticalSpacing = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
inner.setLayout(gridLayout);
|
||||
inner.setLayoutData(new RowData());
|
||||
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.CENTER, true, true);
|
||||
|
||||
final Label imageButton = this.entityTable.widgetFactory.imageButton(
|
||||
ImageIcon.SEARCH,
|
||||
this.composite,
|
||||
inner,
|
||||
new LocTextKey("sebserver.overall.action.filter"),
|
||||
event -> {
|
||||
this.entityTable.applyFilter();
|
||||
});
|
||||
this.entityTable.widgetFactory.imageButton(
|
||||
imageButton.setLayoutData(gridData);
|
||||
final Label imageButton2 = this.entityTable.widgetFactory.imageButton(
|
||||
ImageIcon.CANCEL,
|
||||
this.composite,
|
||||
inner,
|
||||
new LocTextKey("sebserver.overall.action.filter.clear"),
|
||||
event -> {
|
||||
reset();
|
||||
this.entityTable.applyFilter();
|
||||
});
|
||||
imageButton2.setLayoutData(gridData);
|
||||
}
|
||||
|
||||
private static abstract class FilterComponent {
|
||||
|
||||
static final int CELL_WIDTH_ADJUSTMENT = -30;
|
||||
static final int CELL_WIDTH_ADJUSTMENT = -5;
|
||||
|
||||
protected final RowData rowData = new RowData();
|
||||
protected final RowData rowData;
|
||||
final TableFilterAttribute attribute;
|
||||
|
||||
FilterComponent(final TableFilterAttribute attribute) {
|
||||
this.attribute = attribute;
|
||||
this.rowData = new RowData();
|
||||
}
|
||||
|
||||
LinkedMultiValueMap<String, String> putFilterParameter(
|
||||
|
@ -188,6 +207,18 @@ public class TableFilter<ROW extends Entity> {
|
|||
|
||||
return false;
|
||||
}
|
||||
|
||||
protected Composite createInnerComposite(final Composite parent) {
|
||||
final Composite inner = new Composite(parent, SWT.NONE);
|
||||
final GridLayout gridLayout = new GridLayout(1, true);
|
||||
gridLayout.horizontalSpacing = 0;
|
||||
gridLayout.verticalSpacing = 0;
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
inner.setLayout(gridLayout);
|
||||
inner.setLayoutData(this.rowData);
|
||||
return inner;
|
||||
}
|
||||
}
|
||||
|
||||
private class NullFilter extends FilterComponent {
|
||||
|
@ -239,8 +270,11 @@ public class TableFilter<ROW extends Entity> {
|
|||
|
||||
@Override
|
||||
FilterComponent build(final Composite parent) {
|
||||
this.textInput = TableFilter.this.entityTable.widgetFactory.textInput(parent, false);
|
||||
this.textInput.setLayoutData(this.rowData);
|
||||
final Composite innerComposite = createInnerComposite(parent);
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true);
|
||||
|
||||
this.textInput = TableFilter.this.entityTable.widgetFactory.textInput(innerComposite);
|
||||
this.textInput.setLayoutData(gridData);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -265,14 +299,17 @@ public class TableFilter<ROW extends Entity> {
|
|||
|
||||
@Override
|
||||
FilterComponent build(final Composite parent) {
|
||||
final Composite innerComposite = createInnerComposite(parent);
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true);
|
||||
|
||||
this.selector = TableFilter.this.entityTable.widgetFactory
|
||||
.selectionLocalized(
|
||||
ch.ethz.seb.sebserver.gui.widget.Selection.Type.SINGLE,
|
||||
parent,
|
||||
innerComposite,
|
||||
this.attribute.resourceSupplier);
|
||||
this.selector
|
||||
.adaptToControl()
|
||||
.setLayoutData(this.rowData);
|
||||
.setLayoutData(gridData);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -292,13 +329,6 @@ public class TableFilter<ROW extends Entity> {
|
|||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean adaptWidth(final int width) {
|
||||
// NOTE: for some unknown reason RWT acts differently on width-property for text inputs and selectors
|
||||
// this is to adjust selection filter criteria to the list column width
|
||||
return super.adaptWidth(width + 25);
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE: SWT DateTime month-number starting with 0 and joda DateTime with 1!
|
||||
|
@ -312,8 +342,11 @@ public class TableFilter<ROW extends Entity> {
|
|||
|
||||
@Override
|
||||
FilterComponent build(final Composite parent) {
|
||||
this.selector = new DateTime(parent, SWT.DATE | SWT.BORDER);
|
||||
this.selector.setLayoutData(this.rowData);
|
||||
final Composite innerComposite = createInnerComposite(parent);
|
||||
final GridData gridData = new GridData(SWT.FILL, SWT.END, true, true);
|
||||
|
||||
this.selector = new DateTime(innerComposite, SWT.DATE | SWT.BORDER);
|
||||
this.selector.setLayoutData(gridData);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -346,9 +379,9 @@ public class TableFilter<ROW extends Entity> {
|
|||
|
||||
@Override
|
||||
boolean adaptWidth(final int width) {
|
||||
// NOTE: for some unknown reason RWT acts differently on width-property for text inputs and selectors
|
||||
// this is to adjust selection filter criteria to the list column width
|
||||
return super.adaptWidth(width + 25);
|
||||
// NOTE: for some unknown reason RWT acts differently on width-property for date selector
|
||||
// this is to adjust date filter criteria to the list column width
|
||||
return super.adaptWidth(width - 5);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -357,7 +390,7 @@ public class TableFilter<ROW extends Entity> {
|
|||
private class DateRange extends FilterComponent {
|
||||
|
||||
private Composite innerComposite;
|
||||
private final GridData rw1 = new GridData();
|
||||
private final GridData rw1 = new GridData(SWT.FILL, SWT.FILL, true, true);
|
||||
private DateTime fromSelector;
|
||||
private DateTime toSelector;
|
||||
|
||||
|
@ -368,15 +401,24 @@ public class TableFilter<ROW extends Entity> {
|
|||
@Override
|
||||
FilterComponent build(final Composite parent) {
|
||||
this.innerComposite = new Composite(parent, SWT.NONE);
|
||||
final GridLayout gridLayout = new GridLayout(2, true);
|
||||
final GridLayout gridLayout = new GridLayout(2, false);
|
||||
gridLayout.marginHeight = 0;
|
||||
gridLayout.marginWidth = 0;
|
||||
gridLayout.horizontalSpacing = 5;
|
||||
gridLayout.verticalSpacing = 3;
|
||||
this.innerComposite.setLayout(gridLayout);
|
||||
this.innerComposite.setLayoutData(this.rowData);
|
||||
|
||||
TableFilter.this.entityTable.widgetFactory
|
||||
.labelLocalized(this.innerComposite, DATE_FROM_TEXT);
|
||||
this.fromSelector = new DateTime(this.innerComposite, SWT.DATE | SWT.BORDER);
|
||||
this.fromSelector.setLayoutData(this.rw1);
|
||||
|
||||
TableFilter.this.entityTable.widgetFactory
|
||||
.labelLocalized(this.innerComposite, DATE_TO_TEXT);
|
||||
this.toSelector = new DateTime(this.innerComposite, SWT.DATE | SWT.BORDER);
|
||||
this.toSelector.setLayoutData(this.rw1);
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -405,7 +447,7 @@ public class TableFilter<ROW extends Entity> {
|
|||
.withYear(this.toSelector.getYear())
|
||||
.withMonthOfYear(this.toSelector.getMonth() + 1)
|
||||
.withDayOfMonth(this.toSelector.getDay())
|
||||
.withTimeAtStartOfDay();
|
||||
.withTime(23, 59, 59, 0);
|
||||
|
||||
return fromDate.toString(Constants.STANDARD_DATE_TIME_FORMATTER) +
|
||||
Constants.EMBEDDED_LIST_SEPARATOR +
|
||||
|
@ -414,15 +456,6 @@ public class TableFilter<ROW extends Entity> {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean adaptWidth(final int width) {
|
||||
// NOTE: for some unknown reason RWT acts differently on width-property for text inputs and selectors
|
||||
// this is to adjust selection filter criteria to the list column width
|
||||
this.rw1.widthHint = (width - 10) / 2;
|
||||
return super.adaptWidth(width + 25);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ import org.slf4j.LoggerFactory;
|
|||
|
||||
import ch.ethz.seb.sebserver.gbl.model.exam.Indicator.Threshold;
|
||||
import ch.ethz.seb.sebserver.gui.service.i18n.LocTextKey;
|
||||
import ch.ethz.seb.sebserver.gui.service.page.PageService;
|
||||
import ch.ethz.seb.sebserver.gui.widget.Selection.Type;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.CustomVariant;
|
||||
import ch.ethz.seb.sebserver.gui.widget.WidgetFactory.ImageIcon;
|
||||
|
@ -48,10 +49,21 @@ public final class ThresholdList extends Composite {
|
|||
private final GridData valueCell;
|
||||
private final GridData colorCell;
|
||||
private final GridData actionCell;
|
||||
private final Composite updateAnchor;
|
||||
|
||||
ThresholdList(final Composite parent, final WidgetFactory widgetFactory) {
|
||||
this(parent, parent, widgetFactory);
|
||||
}
|
||||
|
||||
ThresholdList(
|
||||
final Composite parent,
|
||||
final Composite updateAnchor,
|
||||
final WidgetFactory widgetFactory) {
|
||||
|
||||
super(parent, SWT.NONE);
|
||||
this.updateAnchor = updateAnchor;
|
||||
this.widgetFactory = widgetFactory;
|
||||
super.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true));
|
||||
|
||||
final GridLayout gridLayout = new GridLayout(3, false);
|
||||
gridLayout.verticalSpacing = 1;
|
||||
|
@ -154,7 +166,8 @@ public final class ThresholdList extends Composite {
|
|||
final Entry entry = new Entry(valueInput, selector, imageButton);
|
||||
this.thresholds.add(entry);
|
||||
|
||||
this.getParent().layout();
|
||||
this.updateAnchor.layout();
|
||||
PageService.updateScrolledComposite(this);
|
||||
}
|
||||
|
||||
private void removeThreshold(final Entry entry) {
|
||||
|
@ -162,7 +175,8 @@ public final class ThresholdList extends Composite {
|
|||
entry.dispose();
|
||||
}
|
||||
|
||||
this.getParent().layout();
|
||||
this.updateAnchor.layout();
|
||||
PageService.updateScrolledComposite(this);
|
||||
}
|
||||
|
||||
private void adaptColumnWidth(final Event event) {
|
||||
|
|
|
@ -114,11 +114,14 @@ public class WidgetFactory {
|
|||
TEXT_H3("h3"),
|
||||
IMAGE_BUTTON("imageButton"),
|
||||
TEXT_ACTION("action"),
|
||||
TEXT_READONLY("readonlyText"),
|
||||
|
||||
FORM_CENTER("form-center"),
|
||||
SELECTION("selection"),
|
||||
SELECTED("selected"),
|
||||
|
||||
ACTIVITY_TREE_SECTION("treesection"),
|
||||
|
||||
FOOTER("footer"),
|
||||
TITLE_LABEL("head"),
|
||||
|
||||
|
@ -532,8 +535,12 @@ public class WidgetFactory {
|
|||
return selection;
|
||||
}
|
||||
|
||||
public ThresholdList thresholdList(final Composite parent, final Collection<Threshold> values) {
|
||||
final ThresholdList thresholdList = new ThresholdList(parent, this);
|
||||
public ThresholdList thresholdList(
|
||||
final Composite parent,
|
||||
final Composite updateAnchor,
|
||||
final Collection<Threshold> values) {
|
||||
|
||||
final ThresholdList thresholdList = new ThresholdList(parent, updateAnchor, this);
|
||||
if (values != null) {
|
||||
thresholdList.setThresholds(values);
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.security.SecureRandom;
|
||||
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -65,10 +66,10 @@ public class ClientCredentialServiceImpl implements ClientCredentialService {
|
|||
|
||||
return new ClientCredentials(
|
||||
clientIdPlaintext,
|
||||
(secretPlaintext != null)
|
||||
(StringUtils.isNoneBlank(secretPlaintext))
|
||||
? encrypt(secretPlaintext, secret).toString()
|
||||
: null,
|
||||
(accessTokenPlaintext != null)
|
||||
(StringUtils.isNoneBlank(accessTokenPlaintext))
|
||||
? encrypt(accessTokenPlaintext, secret).toString()
|
||||
: null);
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ import ch.ethz.seb.sebserver.gbl.model.sebconfig.SebClientConfig;
|
|||
import ch.ethz.seb.sebserver.gbl.model.session.ClientConnection;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent;
|
||||
import ch.ethz.seb.sebserver.gbl.model.session.ClientEvent.EventType;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserInfo;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
|
||||
|
@ -234,6 +235,20 @@ public class FilterMap extends POSTMapper {
|
|||
false);
|
||||
}
|
||||
|
||||
public Long getUserLogFrom(final String filterAttrFrom) {
|
||||
return getFromToValue(
|
||||
UserActivityLog.FILTER_ATTR_FROM_TO,
|
||||
UserActivityLog.FILTER_ATTR_FROM,
|
||||
true);
|
||||
}
|
||||
|
||||
public Long getUserLofTo(final String filterAttrTo) {
|
||||
return getFromToValue(
|
||||
UserActivityLog.FILTER_ATTR_FROM_TO,
|
||||
UserActivityLog.FILTER_ATTR_TO,
|
||||
false);
|
||||
}
|
||||
|
||||
public String getClientEventText() {
|
||||
return getSQLWildcard(ClientEvent.FILTER_ATTR_TEXT);
|
||||
}
|
||||
|
|
|
@ -35,6 +35,12 @@ public interface UserActivityLogDAO extends
|
|||
* @return Result of the Entity or referring to an Error id happened */
|
||||
public <E extends Entity> Result<E> logImport(E entity);
|
||||
|
||||
/** Create a user activity log entry for the current user of activity type EXPORT
|
||||
*
|
||||
* @param entity the Entity
|
||||
* @return Result of the Entity or referring to an Error id happened */
|
||||
public <E extends Entity> Result<E> logExport(E entity);
|
||||
|
||||
/** Create a user activity log entry for the current user of activity type MODIFY
|
||||
*
|
||||
* @param entity the Entity
|
||||
|
|
|
@ -13,7 +13,9 @@ import static org.mybatis.dynamic.sql.SqlBuilder.isIn;
|
|||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
@ -38,10 +40,11 @@ import ch.ethz.seb.sebserver.gbl.util.Result;
|
|||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordMapper;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserRecordMapper;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.UserActivityLogRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.model.UserRecord;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.impl.SEBServerUser;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.DAOLoggingSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.TransactionHandler;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
|
@ -54,13 +57,16 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
private static final Logger log = LoggerFactory.getLogger(UserActivityLogDAOImpl.class);
|
||||
|
||||
private final UserActivityLogRecordMapper userLogRecordMapper;
|
||||
private final UserRecordMapper userRecordMapper;
|
||||
private final UserService userService;
|
||||
|
||||
public UserActivityLogDAOImpl(
|
||||
final UserActivityLogRecordMapper userLogRecordMapper,
|
||||
final UserRecordMapper userRecordMapper,
|
||||
final UserService userService) {
|
||||
|
||||
this.userLogRecordMapper = userLogRecordMapper;
|
||||
this.userRecordMapper = userRecordMapper;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
|
@ -81,6 +87,12 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
return log(UserLogActivityType.IMPORT, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <E extends Entity> Result<E> logExport(final E entity) {
|
||||
return log(UserLogActivityType.EXPORT, entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public <E extends Entity> Result<E> logModify(final E entity) {
|
||||
|
@ -176,7 +188,12 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
final String message) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
log(user, activityType, entity.entityType(), entity.getModelId(), message);
|
||||
String _message = message;
|
||||
if (message == null) {
|
||||
_message = "Entity details: " + String.valueOf(entity);
|
||||
}
|
||||
|
||||
log(user, activityType, entity.entityType(), entity.getModelId(), _message);
|
||||
return entity;
|
||||
})
|
||||
.onError(TransactionHandler::rollback)
|
||||
|
@ -210,7 +227,7 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
@Transactional(readOnly = true)
|
||||
public Result<UserActivityLog> byPK(final Long id) {
|
||||
return Result.tryCatch(() -> this.userLogRecordMapper.selectByPrimaryKey(id))
|
||||
.flatMap(UserActivityLogDAOImpl::toDomainModel);
|
||||
.flatMap(this::toDomainModel);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -235,28 +252,29 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
@Transactional(readOnly = true)
|
||||
public Result<Collection<UserActivityLog>> getAllForUser(final String userUuid) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.userLogRecordMapper.selectByExample()
|
||||
.where(
|
||||
UserActivityLogRecordDynamicSqlSupport.userUuid,
|
||||
SqlBuilder.isEqualTo(userUuid))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(UserActivityLogDAOImpl::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return this.toDomainModel(
|
||||
this.userService.getCurrentUser().institutionId(),
|
||||
this.userLogRecordMapper.selectByExample()
|
||||
.where(
|
||||
UserActivityLogRecordDynamicSqlSupport.userUuid,
|
||||
SqlBuilder.isEqualTo(userUuid))
|
||||
.build()
|
||||
.execute());
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(readOnly = true)
|
||||
public Result<Collection<UserActivityLog>> allMatching(final FilterMap filterMap,
|
||||
public Result<Collection<UserActivityLog>> allMatching(
|
||||
final FilterMap filterMap,
|
||||
final Predicate<UserActivityLog> predicate) {
|
||||
|
||||
return all(
|
||||
filterMap.getInstitutionId(),
|
||||
filterMap.getString(UserActivityLog.FILTER_ATTR_USER),
|
||||
filterMap.getLong(UserActivityLog.FILTER_ATTR_FROM),
|
||||
filterMap.getLong(UserActivityLog.FILTER_ATTR_TO),
|
||||
filterMap.getUserLogFrom(UserActivityLog.FILTER_ATTR_FROM),
|
||||
filterMap.getUserLofTo(UserActivityLog.FILTER_ATTR_TO),
|
||||
filterMap.getString(UserActivityLog.FILTER_ATTR_ACTIVITY_TYPES),
|
||||
filterMap.getString(UserActivityLog.FILTER_ATTR_ENTITY_TYPES),
|
||||
predicate);
|
||||
|
@ -285,37 +303,36 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
? predicate
|
||||
: model -> true;
|
||||
|
||||
return this.userLogRecordMapper.selectByExample()
|
||||
.join(UserRecordDynamicSqlSupport.userRecord)
|
||||
.on(
|
||||
UserRecordDynamicSqlSupport.uuid,
|
||||
SqlBuilder.equalTo(UserActivityLogRecordDynamicSqlSupport.userUuid))
|
||||
.where(
|
||||
UserRecordDynamicSqlSupport.institutionId,
|
||||
SqlBuilder.isEqualTo(institutionId))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.userUuid,
|
||||
SqlBuilder.isEqualToWhenPresent(userId))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.timestamp,
|
||||
SqlBuilder.isGreaterThanOrEqualToWhenPresent(from))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.timestamp,
|
||||
SqlBuilder.isLessThanWhenPresent(to))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.activityType,
|
||||
SqlBuilder.isInCaseInsensitiveWhenPresent(_activityTypes))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.entityType,
|
||||
SqlBuilder.isInCaseInsensitiveWhenPresent(_entityTypes))
|
||||
.build()
|
||||
.execute()
|
||||
return this.toDomainModel(
|
||||
institutionId,
|
||||
this.userLogRecordMapper.selectByExample()
|
||||
.join(UserRecordDynamicSqlSupport.userRecord)
|
||||
.on(
|
||||
UserRecordDynamicSqlSupport.uuid,
|
||||
SqlBuilder.equalTo(UserActivityLogRecordDynamicSqlSupport.userUuid))
|
||||
.where(
|
||||
UserRecordDynamicSqlSupport.institutionId,
|
||||
SqlBuilder.isEqualTo(institutionId))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.userUuid,
|
||||
SqlBuilder.isEqualToWhenPresent(userId))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.timestamp,
|
||||
SqlBuilder.isGreaterThanOrEqualToWhenPresent(from))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.timestamp,
|
||||
SqlBuilder.isLessThanWhenPresent(to))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.activityType,
|
||||
SqlBuilder.isInCaseInsensitiveWhenPresent(_activityTypes))
|
||||
.and(
|
||||
UserActivityLogRecordDynamicSqlSupport.entityType,
|
||||
SqlBuilder.isInCaseInsensitiveWhenPresent(_entityTypes))
|
||||
.build()
|
||||
.execute())
|
||||
.stream()
|
||||
.map(UserActivityLogDAOImpl::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.filter(_predicate)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -323,14 +340,13 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
@Transactional(readOnly = true)
|
||||
public Result<Collection<UserActivityLog>> allOf(final Set<Long> pks) {
|
||||
return Result.tryCatch(() -> {
|
||||
return this.userLogRecordMapper.selectByExample()
|
||||
.where(UserActivityLogRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.map(UserActivityLogDAOImpl::toDomainModel)
|
||||
.flatMap(DAOLoggingSupport::logAndSkipOnError)
|
||||
.collect(Collectors.toList());
|
||||
|
||||
return this.toDomainModel(
|
||||
this.userService.getCurrentUser().institutionId(),
|
||||
this.userLogRecordMapper.selectByExample()
|
||||
.where(UserActivityLogRecordDynamicSqlSupport.id, isIn(new ArrayList<>(pks)))
|
||||
.build()
|
||||
.execute());
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -395,11 +411,67 @@ public class UserActivityLogDAOImpl implements UserActivityLogDAO {
|
|||
this.userLogRecordMapper.updateByPrimaryKeySelective(selective);
|
||||
}
|
||||
|
||||
private static Result<UserActivityLog> toDomainModel(final UserActivityLogRecord record) {
|
||||
private Collection<UserActivityLog> toDomainModel(
|
||||
final Long institutionId,
|
||||
final List<UserActivityLogRecord> records) {
|
||||
|
||||
if (records.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
final Set<String> useruuids = records
|
||||
.stream()
|
||||
.map(UserActivityLogRecord::getUserUuid)
|
||||
.collect(Collectors.toSet());
|
||||
|
||||
final Map<String, String> userMapping = this.userRecordMapper
|
||||
.selectByExample()
|
||||
.where(
|
||||
UserRecordDynamicSqlSupport.institutionId,
|
||||
SqlBuilder.isEqualToWhenPresent(institutionId))
|
||||
.and(
|
||||
UserRecordDynamicSqlSupport.uuid,
|
||||
SqlBuilder.isIn(new ArrayList<>(useruuids)))
|
||||
.build()
|
||||
.execute()
|
||||
.stream()
|
||||
.collect(Collectors.toMap(ur -> ur.getUuid(), ur -> ur.getUsername()));
|
||||
|
||||
return records
|
||||
.stream()
|
||||
.map(record -> new UserActivityLog(
|
||||
record.getId(),
|
||||
record.getUserUuid(),
|
||||
userMapping.get(record.getUserUuid()),
|
||||
record.getTimestamp(),
|
||||
UserLogActivityType.valueOf(record.getActivityType()),
|
||||
EntityType.valueOf(record.getEntityType()),
|
||||
record.getEntityId(),
|
||||
record.getMessage()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private Result<UserActivityLog> toDomainModel(final UserActivityLogRecord record) {
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
final List<UserRecord> user = this.userRecordMapper.selectByExample()
|
||||
.where(
|
||||
UserRecordDynamicSqlSupport.uuid,
|
||||
SqlBuilder.isEqualTo(record.getUserUuid()))
|
||||
.build()
|
||||
.execute();
|
||||
|
||||
String username = record.getUserUuid();
|
||||
if (CollectionUtils.isEmpty(user) || user.size() > 1) {
|
||||
log.error("To many user found for user uuid: {}", record.getUserUuid());
|
||||
} else {
|
||||
username = user.get(0).getUsername();
|
||||
}
|
||||
|
||||
return new UserActivityLog(
|
||||
record.getId(),
|
||||
record.getUserUuid(),
|
||||
username,
|
||||
record.getTimestamp(),
|
||||
UserLogActivityType.valueOf(record.getActivityType()),
|
||||
EntityType.valueOf(record.getEntityType()),
|
||||
|
|
|
@ -33,9 +33,6 @@ final class MockupLmsAPITemplate implements LmsAPITemplate {
|
|||
|
||||
private static final Logger log = LoggerFactory.getLogger(MockupLmsAPITemplate.class);
|
||||
|
||||
public static final String MOCKUP_LMS_CLIENT_NAME = "mockupLmsClientName";
|
||||
public static final String MOCKUP_LMS_CLIENT_SECRET = "mockupLmsClientSecret";
|
||||
|
||||
private final ClientCredentialService clientCredentialService;
|
||||
private final LmsSetup lmsSetup;
|
||||
private final ClientCredentials credentials;
|
||||
|
|
|
@ -19,7 +19,7 @@ import java.util.function.Predicate;
|
|||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.tomcat.util.http.fileupload.IOUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
|
@ -78,31 +78,31 @@ public class ExamConfigIO {
|
|||
final ConfigurationFormat exportFormat,
|
||||
final OutputStream out,
|
||||
final Long institutionId,
|
||||
final Long configurationNodeId) {
|
||||
final Long configurationNodeId) throws Exception {
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Start export SEB plain XML configuration asynconously");
|
||||
}
|
||||
|
||||
// get all defined root configuration attributes prepared and sorted
|
||||
final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.flatMap(this::convertAttribute)
|
||||
.filter(exportFormatBasedAttributeFilter(exportFormat))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// get follow-up configurationId for given configurationNodeId
|
||||
final Long configurationId = this.configurationDAO
|
||||
.getFollowupConfiguration(configurationNodeId)
|
||||
.getOrThrow().id;
|
||||
|
||||
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
|
||||
getConfigurationValueSupplier(institutionId, configurationId);
|
||||
|
||||
try {
|
||||
|
||||
// get all defined root configuration attributes prepared and sorted
|
||||
final List<ConfigurationAttribute> sortedAttributes = this.configurationAttributeDAO.getAllRootAttributes()
|
||||
.getOrThrow()
|
||||
.stream()
|
||||
.flatMap(this::convertAttribute)
|
||||
.filter(exportFormatBasedAttributeFilter(exportFormat))
|
||||
.sorted()
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// get follow-up configurationId for given configurationNodeId
|
||||
final Long configurationId = this.configurationDAO
|
||||
.getFollowupConfiguration(configurationNodeId)
|
||||
.getOrThrow().id;
|
||||
|
||||
final Function<ConfigurationAttribute, ConfigurationValue> configurationValueSupplier =
|
||||
getConfigurationValueSupplier(institutionId, configurationId);
|
||||
|
||||
writeHeader(exportFormat, out);
|
||||
|
||||
// write attributes
|
||||
|
@ -135,7 +135,6 @@ public class ExamConfigIO {
|
|||
}
|
||||
|
||||
writeFooter(exportFormat, out);
|
||||
out.flush();
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("Finished export SEB plain XML configuration asynconously");
|
||||
|
@ -143,12 +142,13 @@ public class ExamConfigIO {
|
|||
|
||||
} catch (final Exception e) {
|
||||
log.error("Unexpected error while trying to write SEB Exam Configuration XML to output stream: ", e);
|
||||
throw e;
|
||||
} finally {
|
||||
try {
|
||||
out.flush();
|
||||
} catch (final IOException e1) {
|
||||
log.error("Unable to flush output stream after error");
|
||||
}
|
||||
} finally {
|
||||
IOUtils.closeQuietly(out);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -66,7 +66,6 @@ public class PasswordEncryptor implements SebConfigCryptor {
|
|||
|
||||
input.close();
|
||||
encryptOutput.flush();
|
||||
encryptOutput.close();
|
||||
|
||||
} catch (final CryptorException e) {
|
||||
log.error("Error while trying to stream and encrypt data: ", e);
|
||||
|
|
|
@ -183,6 +183,10 @@ public class SebClientConfigServiceImpl implements SebClientConfigService {
|
|||
EncryptionContext.contextOfPlainText());
|
||||
}
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("*** Finished Seb client configuration download streaming composition");
|
||||
}
|
||||
|
||||
} catch (final Exception e) {
|
||||
log.error("Error while zip and encrypt seb client config stream: ", e);
|
||||
try {
|
||||
|
@ -218,7 +222,8 @@ public class SebClientConfigServiceImpl implements SebClientConfigService {
|
|||
return plainTextConfig;
|
||||
}
|
||||
|
||||
private String extractXML(final SebClientConfig config,
|
||||
private String extractXML(
|
||||
final SebClientConfig config,
|
||||
final CharSequence plainClientId,
|
||||
final CharSequence plainClientSecret) {
|
||||
|
||||
|
@ -294,10 +299,6 @@ public class SebClientConfigServiceImpl implements SebClientConfigService {
|
|||
EncryptionContext.contextOf(
|
||||
Strategy.PASSWORD_PSWD,
|
||||
encryptionPasswordPlaintext));
|
||||
|
||||
if (log.isDebugEnabled()) {
|
||||
log.debug("*** Finished Seb client configuration with password based encryption");
|
||||
}
|
||||
}
|
||||
|
||||
/** Get a encoded clientSecret for the SebClientConfiguration with specified clientId/clientName.
|
||||
|
|
|
@ -73,6 +73,7 @@ public final class SebConfigEncryptionServiceImpl implements SebConfigEncryption
|
|||
}
|
||||
|
||||
pout.write(strategy.header);
|
||||
|
||||
getEncryptor(strategy)
|
||||
.getOrThrow()
|
||||
.encrypt(pout, input, context);
|
||||
|
@ -92,12 +93,6 @@ public final class SebConfigEncryptionServiceImpl implements SebConfigEncryption
|
|||
} catch (final IOException e1) {
|
||||
log.error("Failed to close PipedInputStream: ", e1);
|
||||
}
|
||||
try {
|
||||
if (pout != null)
|
||||
pout.close();
|
||||
} catch (final IOException e1) {
|
||||
log.error("Failed to close PipedOutputStream: ", e1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -203,7 +203,7 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
|
|||
|
||||
return Result.of(configKey);
|
||||
|
||||
} catch (final IOException e) {
|
||||
} catch (final Exception e) {
|
||||
log.error("Error while stream plain JSON SEB clonfiguration data for Config-Key generation: ", e);
|
||||
return Result.ofError(e);
|
||||
} finally {
|
||||
|
@ -254,7 +254,7 @@ public class SebExamConfigServiceImpl implements SebExamConfigService {
|
|||
pout.close();
|
||||
pin.close();
|
||||
|
||||
} catch (final IOException e) {
|
||||
} catch (final Exception e) {
|
||||
log.error("Error while stream plain text SEB clonfiguration data: ", e);
|
||||
} finally {
|
||||
try {
|
||||
|
|
|
@ -131,7 +131,8 @@ public class ConfigurationNodeController extends EntityController<ConfigurationN
|
|||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId) {
|
||||
|
||||
this.entityDAO.byPK(modelId)
|
||||
.flatMap(this.authorization::checkRead);
|
||||
.flatMap(this.authorization::checkRead)
|
||||
.map(this.userActivityLogDAO::logExport);
|
||||
|
||||
final StreamingResponseBody stream = out -> this.sebExamConfigService
|
||||
.exportPlainXML(out, institutionId, modelId);
|
||||
|
|
|
@ -26,10 +26,12 @@ import ch.ethz.seb.sebserver.gbl.Constants;
|
|||
import ch.ethz.seb.sebserver.gbl.api.API;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage;
|
||||
import ch.ethz.seb.sebserver.gbl.api.APIMessage.APIMessageException;
|
||||
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.sebconfig.SebClientConfig;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.PasswordChange;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserLogActivityType;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.SebClientConfigRecordDynamicSqlSupport;
|
||||
|
@ -76,10 +78,20 @@ public class SebClientConfigController extends ActivatableEntityController<SebCl
|
|||
@PathVariable final String modelId) {
|
||||
|
||||
this.entityDAO.byModelId(modelId)
|
||||
.map(this.authorization::checkWrite);
|
||||
.flatMap(this.authorization::checkWrite)
|
||||
.map(this.userActivityLogDAO::logExport);
|
||||
|
||||
final StreamingResponseBody stream = out -> this.sebClientConfigService
|
||||
.exportSebClientConfiguration(out, modelId);
|
||||
this.userActivityLogDAO.log(
|
||||
UserLogActivityType.EXPORT,
|
||||
EntityType.SEB_CLIENT_CONFIGURATION,
|
||||
modelId,
|
||||
"Export of SEB Client Configuration");
|
||||
|
||||
final StreamingResponseBody stream = out -> {
|
||||
this.sebClientConfigService.exportSebClientConfiguration(
|
||||
out,
|
||||
modelId);
|
||||
};
|
||||
|
||||
return new ResponseEntity<>(stream, HttpStatus.OK);
|
||||
}
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
|
||||
package ch.ethz.seb.sebserver.webservice.weblayer.api;
|
||||
|
||||
import java.util.Collection;
|
||||
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.web.bind.WebDataBinder;
|
||||
import org.springframework.web.bind.annotation.InitBinder;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
|
@ -23,12 +23,11 @@ import ch.ethz.seb.sebserver.gbl.api.authorization.PrivilegeType;
|
|||
import ch.ethz.seb.sebserver.gbl.model.Page;
|
||||
import ch.ethz.seb.sebserver.gbl.model.user.UserActivityLog;
|
||||
import ch.ethz.seb.sebserver.gbl.profile.WebServiceProfile;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Result;
|
||||
import ch.ethz.seb.sebserver.gbl.util.Utils;
|
||||
import ch.ethz.seb.sebserver.webservice.datalayer.batis.mapper.UserActivityLogRecordDynamicSqlSupport;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.PaginationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.AuthorizationService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.authorization.UserService;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.FilterMap;
|
||||
import ch.ethz.seb.sebserver.webservice.servicelayer.dao.UserActivityLogDAO;
|
||||
|
||||
@WebServiceProfile
|
||||
|
@ -57,53 +56,50 @@ public class UserActivityLogController {
|
|||
.addUsersInstitutionDefaultPropertySupport(binder);
|
||||
}
|
||||
|
||||
@RequestMapping(method = RequestMethod.GET)
|
||||
/** Rest endpoint to get a Page UserActivityLog.
|
||||
*
|
||||
* GET /{api}/{entity-type-endpoint-name}
|
||||
*
|
||||
* GET /admin-api/v1/useractivity
|
||||
* GET /admin-api/v1/useractivity?page_number=2&page_size=10&sort=-name
|
||||
* GET /admin-api/v1/useractivity?name=seb&active=true
|
||||
*
|
||||
* @param institutionId The institution identifier of the request.
|
||||
* Default is the institution identifier of the institution of the current user
|
||||
* @param pageNumber the number of the page that is requested
|
||||
* @param pageSize the size of the page that is requested
|
||||
* @param sort the sort parameter to sort the list of entities before paging
|
||||
* the sort parameter is the name of the entity-model attribute to sort with a leading '-' sign for
|
||||
* descending sort order
|
||||
* @param allRequestParams a MultiValueMap of all request parameter that is used for filtering
|
||||
* @return Page of domain-model-entities of specified type */
|
||||
@RequestMapping(
|
||||
method = RequestMethod.GET,
|
||||
consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE,
|
||||
produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
|
||||
public Page<UserActivityLog> getPage(
|
||||
@RequestParam(
|
||||
name = UserActivityLog.FILTER_ATTR_INSTITUTION,
|
||||
name = API.PARAM_INSTITUTION_ID,
|
||||
required = true,
|
||||
defaultValue = UserService.USERS_INSTITUTION_AS_DEFAULT) final Long institutionId,
|
||||
@RequestParam(name = UserActivityLog.FILTER_ATTR_USER, required = false) final String userId,
|
||||
@RequestParam(name = UserActivityLog.FILTER_ATTR_FROM, required = false) final String from,
|
||||
@RequestParam(name = UserActivityLog.FILTER_ATTR_TO, required = false) final String to,
|
||||
@RequestParam(name = UserActivityLog.FILTER_ATTR_ACTIVITY_TYPES,
|
||||
required = false) final String activityTypes,
|
||||
@RequestParam(name = UserActivityLog.FILTER_ATTR_ENTITY_TYPES, required = false) final String entityTypes,
|
||||
@RequestParam(name = Page.ATTR_PAGE_NUMBER, required = false) final Integer pageNumber,
|
||||
@RequestParam(name = Page.ATTR_PAGE_SIZE, required = false) final Integer pageSize,
|
||||
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort) {
|
||||
@RequestParam(name = Page.ATTR_SORT, required = false) final String sort,
|
||||
@RequestParam final MultiValueMap<String, String> allRequestParams) {
|
||||
|
||||
// at least current user must have read access for specified entity type within its own institution
|
||||
checkBaseReadPrivilege(institutionId);
|
||||
return this.paginationService.getPage(
|
||||
|
||||
final FilterMap filterMap = new FilterMap(allRequestParams);
|
||||
filterMap.putIfAbsent(API.PARAM_INSTITUTION_ID, String.valueOf(institutionId));
|
||||
|
||||
return this.paginationService.<UserActivityLog> getPage(
|
||||
pageNumber,
|
||||
pageSize,
|
||||
sort,
|
||||
UserActivityLogRecordDynamicSqlSupport.userActivityLogRecord.name(),
|
||||
() -> _getAll(institutionId, userId, from, to, activityTypes, entityTypes)).getOrThrow();
|
||||
}
|
||||
|
||||
private Result<Collection<UserActivityLog>> _getAll(
|
||||
final Long institutionId,
|
||||
final String userId,
|
||||
final String from,
|
||||
final String to,
|
||||
final String activityTypes,
|
||||
final String entityTypes) {
|
||||
|
||||
return Result.tryCatch(() -> {
|
||||
|
||||
this.paginationService.setDefaultLimitIfNotSet();
|
||||
|
||||
return this.userActivityLogDAO.all(
|
||||
institutionId,
|
||||
userId,
|
||||
Utils.toTimestamp(from),
|
||||
Utils.toTimestamp(to),
|
||||
activityTypes,
|
||||
entityTypes,
|
||||
Utils.truePredicate())
|
||||
.getOrThrow();
|
||||
});
|
||||
() -> this.userActivityLogDAO.allMatching(filterMap))
|
||||
.getOrThrow();
|
||||
}
|
||||
|
||||
private void checkBaseReadPrivilege(final Long institutionId) {
|
||||
|
|
|
@ -31,7 +31,7 @@ INSERT IGNORE INTO view VALUES
|
|||
(11, 'hooked_keys', 12, 11);
|
||||
|
||||
INSERT IGNORE INTO lms_setup VALUES
|
||||
(1, 1, 'test', 'MOCKUP', 'http://', 'ccdfa2330533ed6c316a8ffbd64a3197d4a79956ac7ee4c1162f7bdb1a27234fe8793615a51074351e', '8d14b78ecdcbec1d010d414a7208dbe5c411f1fa735c35c7427d840453093a3730d1bc0abe13b9b1a8', null, 1)
|
||||
(1, 1, 'test', 'MOCKUP', 'http://', 'test-user', '8d14b78ecdcbec1d010d414a7208dbe5c411f1fa735c35c7427d840453093a3730d1bc0abe13b9b1a8', null, 1)
|
||||
;
|
||||
|
||||
INSERT IGNORE INTO seb_client_configuration VALUES
|
||||
|
|
|
@ -32,7 +32,7 @@
|
|||
<Logger name="org.springframework.web" level="INFO" additivity="true" />
|
||||
<Logger name="org.springframework.security.oauth2" level="INFO" additivity="true" />
|
||||
|
||||
<Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebClientConfigServiceImpl" level="DEBUG" additivity="true" />
|
||||
<Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig" level="DEBUG" additivity="true" />
|
||||
<Logger name="ch.ethz.seb.sebserver.webservice.servicelayer.sebconfig.impl.SebExamConfigServiceImpl" level="TRACE" additivity="true" />
|
||||
|
||||
</springProfile>
|
||||
|
|
|
@ -21,10 +21,37 @@ sebserver.overall.action.category.varia=Varia
|
|||
sebserver.overall.status.active=Active
|
||||
sebserver.overall.status.inactive=Inactive
|
||||
|
||||
sebserver.overall.date.from=From
|
||||
sebserver.overall.date.to=To
|
||||
|
||||
sebserver.overall.action.add=Add;
|
||||
sebserver.overall.action.remove=Remove
|
||||
sebserver.overall.action.select=Please Select
|
||||
|
||||
sebserver.overall.types.activityType.CREATE=Create New
|
||||
sebserver.overall.types.activityType.IMPORT=Import
|
||||
sebserver.overall.types.activityType.EXPORT=Export
|
||||
sebserver.overall.types.activityType.MODIFY=Modify
|
||||
sebserver.overall.types.activityType.PASSWORD_CHANGE=Password Change
|
||||
sebserver.overall.types.activityType.DEACTIVATE=Deactivate
|
||||
sebserver.overall.types.activityType.ACTIVATE=Activate
|
||||
sebserver.overall.types.activityType.DELETE=Delete
|
||||
|
||||
sebserver.overall.types.entityType.CONFIGURATION_ATTRIBUTE=Configuration Attribute
|
||||
sebserver.overall.types.entityType.CONFIGURATION_VALUE=Configuration Value
|
||||
sebserver.overall.types.entityType.VIEW=Configuration View
|
||||
sebserver.overall.types.entityType.ORIENTATION=Configuration Orientation
|
||||
sebserver.overall.types.entityType.CONFIGURATION=Configuration History Point
|
||||
sebserver.overall.types.entityType.CONFIGURATION_NODE=Exam Configuration
|
||||
sebserver.overall.types.entityType.EXAM_CONFIGURATION_MAP=Exam Configuration Mapping
|
||||
sebserver.overall.types.entityType.EXAM=Exam
|
||||
sebserver.overall.types.entityType.INDICATOR=Indicator
|
||||
sebserver.overall.types.entityType.THRESHOLD=Threshold
|
||||
sebserver.overall.types.entityType.INSTITUTION=Institution
|
||||
sebserver.overall.types.entityType.SEB_CLIENT_CONFIGURATION=Client Configuration
|
||||
sebserver.overall.types.entityType.LMS_SETUP=LMS Setup
|
||||
sebserver.overall.types.entityType.USER=User Account
|
||||
|
||||
################################
|
||||
# Form validation and messages
|
||||
################################
|
||||
|
@ -288,6 +315,7 @@ sebserver.exam.configuration.list.column.name=Name
|
|||
sebserver.exam.configuration.list.column.description=Description
|
||||
sebserver.exam.configuration.list.column.status=Status
|
||||
sebserver.exam.configuration.list.empty=There is currently no SEB Configuration defined for this Exam. Please add one
|
||||
sebserver.exam.configuration.list.pleaseSelect=Please Select a SEB Configuration first
|
||||
|
||||
sebserver.exam.configuration.action.list.new=Add
|
||||
sebserver.exam.configuration.action.list.modify=Edit
|
||||
|
@ -877,7 +905,7 @@ sebserver.monitoring.exam.action.detail.view=Back To Overview
|
|||
sebserver.monitoring.exam.action.list.view=Monitoring
|
||||
|
||||
|
||||
sebserver.monitoring.exam.info.pleaseSelect=Please select an exam first
|
||||
sebserver.monitoring.exam.info.pleaseSelect=Please select an exam from the list
|
||||
sebserver.monitoring.exam.list.empty=There are currently no running exams
|
||||
|
||||
sebserver.monitoring.exam.list.column.name=Name
|
||||
|
@ -893,7 +921,7 @@ sebserver.monitoring.connection.list.column.status=Status
|
|||
sebserver.monitoring.connection.list.column.examname=Exam
|
||||
sebserver.monitoring.connection.list.column.vdiAddress=IP Address (VDI)
|
||||
|
||||
sebserver.monitoring.exam.connection.emptySelection=Please select a connection first
|
||||
sebserver.monitoring.exam.connection.emptySelection=Please select a connection from the list
|
||||
sebserver.monitoring.exam.connection.title=SEB Client Connection
|
||||
sebserver.monitoring.exam.connection.list.actions=Selected Connection
|
||||
sebserver.monitoring.exam.connection.action.view=View Details
|
||||
|
@ -913,3 +941,31 @@ sebserver.monitoring.exam.connection.event.type.WARN_LOG=Warn
|
|||
sebserver.monitoring.exam.connection.event.type.ERROR_LOG=Error
|
||||
sebserver.monitoring.exam.connection.event.type.LAST_PING=Last Ping
|
||||
|
||||
################################
|
||||
# Logs
|
||||
################################
|
||||
|
||||
sebserver.logs.activity.main=Logs
|
||||
sebserver.logs.activity.userlogs=User Logs
|
||||
sebserver.logs.activity.userlogs.details=Show Details
|
||||
sebserver.logs.activity.seblogs=SEB Client Logs
|
||||
sebserver.logs.activity.seblogs.details=Show Details
|
||||
|
||||
sebserver.userlogs.list.title=User Activity Logs
|
||||
sebserver.userlogs.list.column.institution=Institution
|
||||
sebserver.userlogs.list.column.user=User
|
||||
sebserver.userlogs.list.column.dateTime=Date
|
||||
sebserver.userlogs.list.column.activityType=User Activity
|
||||
sebserver.userlogs.list.column.entityType=Entity
|
||||
sebserver.userlogs.list.column.message=Message
|
||||
|
||||
sebserver.userlogs.details.title=User Activity Log Details
|
||||
sebserver.userlogs.info.pleaseSelect=Please select a log from the list
|
||||
sebserver.userlogs.list.actions=Selected Log
|
||||
sebserver.userlogs.list.empty=No User activity logs has been found. Please adapt or clear the filter
|
||||
|
||||
|
||||
sebserver.seblogs.list.title=SEB Client Logs
|
||||
sebserver.seblogs.list.actions=Selected Log
|
||||
sebserver.seblogs.list.empty=No SEB client logs has been found. Please adapt or clear the filter
|
||||
|
||||
|
|
|
@ -439,7 +439,7 @@ CREATE TABLE IF NOT EXISTS `user_activity_log` (
|
|||
`activity_type` VARCHAR(45) NOT NULL,
|
||||
`entity_type` VARCHAR(45) NOT NULL,
|
||||
`entity_id` VARCHAR(255) NOT NULL,
|
||||
`message` VARCHAR(255) NULL,
|
||||
`message` VARCHAR(4000) NULL,
|
||||
PRIMARY KEY (`id`))
|
||||
;
|
||||
|
||||
|
|
|
@ -461,7 +461,7 @@ CREATE TABLE IF NOT EXISTS `user_activity_log` (
|
|||
`activity_type` VARCHAR(45) NOT NULL,
|
||||
`entity_type` VARCHAR(45) NOT NULL,
|
||||
`entity_id` VARCHAR(255) NOT NULL,
|
||||
`message` VARCHAR(255) NULL,
|
||||
`message` VARCHAR(4000) NULL,
|
||||
PRIMARY KEY (`id`))
|
||||
;
|
||||
|
||||
|
|
|
@ -166,8 +166,8 @@ Composite.login {
|
|||
}
|
||||
|
||||
Composite.error {
|
||||
background-color: #aa0000;
|
||||
margin: 0 0 0 0;
|
||||
border: 1px solid #aa0000;
|
||||
border-radius: 1px;
|
||||
}
|
||||
|
||||
*.header {
|
||||
|
@ -230,11 +230,6 @@ Text.error {
|
|||
border: 1px solid #aa0000;
|
||||
}
|
||||
|
||||
Text[MULTI] {
|
||||
padding: 5px 10px 5px 10px;
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
Text[BORDER], Text[MULTI][BORDER] {
|
||||
border: 1px solid #aaaaaa;
|
||||
border-radius: 0;
|
||||
|
@ -252,10 +247,13 @@ Text[BORDER]:focused, Text[MULTI][BORDER]:focused {
|
|||
box-shadow: none;
|
||||
}
|
||||
|
||||
Text[BORDER]:disabled, Text[MULTI][BORDER]:disabled, Text[BORDER]:read-only,
|
||||
Text[MULTI][BORDER]:read-only {
|
||||
Text:disabled, Text:read-only, Text[BORDER]:disabled, Text[MULTI]:disabled, Text[MULTI][BORDER]:disabled, Text[BORDER]:read-only, Text[MULTI]:read-only, Text[MULTI][BORDER]:read-only {
|
||||
box-shadow: none;
|
||||
background-color: #f0f0f0;
|
||||
background-color: #ffffff;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
color: #4a4a4a;
|
||||
padding: 0px 0px 0px 0px;
|
||||
}
|
||||
|
||||
|
||||
|
@ -374,6 +372,12 @@ Shell-Titlebar.message {
|
|||
text-shadow: none;
|
||||
}
|
||||
|
||||
Shell-CloseButton:hover.message {
|
||||
background-color: #82BE1E;
|
||||
background-gradient-color: #82BE1E;
|
||||
background-image: gradient( linear, left top, left bottom, from( #82BE1E ), to( #82BE1E ) );
|
||||
}
|
||||
|
||||
Button {
|
||||
font: 12px Arial, Helvetica, sans-serif;
|
||||
padding: 5px 6px 5px 6px;
|
||||
|
@ -488,7 +492,7 @@ Tree[BORDER] {
|
|||
border: 1px solid #eceeef;
|
||||
}
|
||||
|
||||
TreeItem {
|
||||
TreeItem, TreeItem.treesection, Tree-RowOverlay:hover.treesection, Tree-RowOverlay:selected.treesection, Tree-RowOverlay:selected:hover.treesection {
|
||||
font: bold 14px Arial, Helvetica, sans-serif;
|
||||
color: #1f407a;
|
||||
background-color: transparent;
|
||||
|
@ -515,6 +519,7 @@ Tree-RowOverlay:hover {
|
|||
color: #1F407A;
|
||||
}
|
||||
|
||||
|
||||
Tree-RowOverlay:selected {
|
||||
background-color: #82be1e;
|
||||
color: #1F407A;
|
||||
|
@ -563,30 +568,7 @@ Tree-RowOverlay:selected.actions {
|
|||
color: #4a4a4a;
|
||||
}
|
||||
|
||||
Tree-Indent {
|
||||
width: 16px;
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
Tree-Indent:collapsed {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
Tree-Indent:collapsed:hover {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
Tree-Indent:expanded {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
Tree-Indent:expanded:hover {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
Tree-Indent:line {
|
||||
background-image: none;
|
||||
}
|
||||
|
||||
/* TabFolder default theme */
|
||||
|
||||
|
|
|
@ -26,6 +26,7 @@ public class UserActivityLogTest {
|
|||
final UserActivityLog testModel = new UserActivityLog(
|
||||
1L,
|
||||
"testUser",
|
||||
"testUser",
|
||||
123l,
|
||||
UserLogActivityType.CREATE,
|
||||
EntityType.EXAM,
|
||||
|
@ -35,12 +36,7 @@ public class UserActivityLogTest {
|
|||
final String jsonValue = this.jsonMapper.writeValueAsString(testModel);
|
||||
|
||||
assertEquals(
|
||||
"{\"userUuid\":\"testUser\","
|
||||
+ "\"timestamp\":123,"
|
||||
+ "\"activityType\":\"CREATE\","
|
||||
+ "\"entityType\":\"EXAM\","
|
||||
+ "\"entityId\":\"321\","
|
||||
+ "\"message\":\"noComment\"}",
|
||||
"{\"id\":1,\"userUuid\":\"testUser\",\"username\":\"testUser\",\"timestamp\":123,\"activityType\":\"CREATE\",\"entityType\":\"EXAM\",\"entityId\":\"321\",\"message\":\"noComment\"}",
|
||||
jsonValue);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.stream.Stream;
|
|||
import org.joda.time.DateTime;
|
||||
import org.joda.time.DateTimeZone;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
|
@ -466,7 +467,8 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
|
|||
this.mockMvc.perform(
|
||||
get(this.endpoint + API.USER_ACCOUNT_ENDPOINT + "/" + createdUser.uuid)
|
||||
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<UserInfo>() {
|
||||
|
@ -482,7 +484,9 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
|
|||
.perform(
|
||||
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
|
||||
+ "?user=user1&activity_types=CREATE")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -1003,7 +1007,8 @@ public class UserAPITest extends AdministrationAPIIntegrationTester {
|
|||
this.mockMvc
|
||||
.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
|
||||
+ "?user=user1&from=" + timeNow)
|
||||
.header("Authorization", "Bearer " + sebAdminToken))
|
||||
.header("Authorization", "Bearer " + sebAdminToken)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
|
|
@ -14,6 +14,8 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Test;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.context.jdbc.Sql;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
|
@ -31,7 +33,8 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
final String token = getSebAdminAccess();
|
||||
final Page<UserActivityLog> logs = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -48,7 +51,8 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
Page<UserActivityLog> logs = this.jsonMapper.readValue(
|
||||
this.mockMvc
|
||||
.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?user=user4&institutionId=2")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -60,7 +64,8 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
// for a user in the same institution no institution is needed
|
||||
logs = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?user=user2")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -83,7 +88,8 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
Page<UserActivityLog> logs = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(
|
||||
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?institutionId=2&from=" + sec2)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -96,7 +102,8 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
this.mockMvc
|
||||
.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?institutionId=2&from="
|
||||
+ sec2 + "&to=" + sec4)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -110,7 +117,9 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
.perform(
|
||||
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?institutionId=2&from=" + sec2
|
||||
+ "&to=" + sec5)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -124,7 +133,9 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
.perform(
|
||||
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?institutionId=2&from=" + sec2
|
||||
+ "&to=" + sec6)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -139,7 +150,8 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
final String token = getSebAdminAccess();
|
||||
Page<UserActivityLog> logs = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?activity_types=CREATE")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -153,7 +165,9 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
.perform(
|
||||
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
|
||||
+ "?activity_types=CREATE,MODIFY")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -168,7 +182,9 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
.perform(
|
||||
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
|
||||
+ "?institutionId=2&activity_types=CREATE,MODIFY")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -184,7 +200,8 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
Page<UserActivityLog> logs = this.jsonMapper.readValue(
|
||||
this.mockMvc
|
||||
.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?entity_types=INSTITUTION")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -198,7 +215,9 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
.perform(
|
||||
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
|
||||
+ "?entity_types=INSTITUTION,EXAM")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -212,7 +231,9 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
.perform(
|
||||
get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT
|
||||
+ "?entity_types=INSTITUTION,EXAM&institutionId=2")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE,
|
||||
MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -227,7 +248,8 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
final String token = getAdminInstitution1Access();
|
||||
final Page<UserActivityLog> logs = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
@ -243,18 +265,21 @@ public class UserActivityLogAPITest extends AdministrationAPIIntegrationTester {
|
|||
|
||||
// no privilege at all
|
||||
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT)
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isForbidden());
|
||||
// no privilege at all
|
||||
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?user=user4")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isForbidden());
|
||||
|
||||
// no privilege to query logs of users of other institution
|
||||
token = getAdminInstitution1Access();
|
||||
final Page<UserActivityLog> logs = this.jsonMapper.readValue(
|
||||
this.mockMvc.perform(get(this.endpoint + API.USER_ACTIVITY_LOG_ENDPOINT + "?user=user4")
|
||||
.header("Authorization", "Bearer " + token))
|
||||
.header("Authorization", "Bearer " + token)
|
||||
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn().getResponse().getContentAsString(),
|
||||
new TypeReference<Page<UserActivityLog>>() {
|
||||
|
|
|
@ -445,7 +445,7 @@ CREATE TABLE IF NOT EXISTS `user_activity_log` (
|
|||
`activity_type` VARCHAR(45) NOT NULL,
|
||||
`entity_type` VARCHAR(45) NOT NULL,
|
||||
`entity_id` VARCHAR(255) NOT NULL,
|
||||
`message` VARCHAR(255) NULL,
|
||||
`message` VARCHAR(4000) NULL,
|
||||
PRIMARY KEY (`id`))
|
||||
;
|
||||
|
||||
|
|
Loading…
Reference in a new issue