SEBSERV-26 user activity logs

This commit is contained in:
anhefti 2019-07-31 17:34:42 +02:00
parent 86720923c5
commit e5f8a995e6
54 changed files with 1158 additions and 372 deletions

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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();
}
}

View file

@ -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=");

View file

@ -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,

View file

@ -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

View file

@ -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,

View file

@ -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()));
}

View file

@ -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) {

View file

@ -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
}
}

View file

@ -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())

View file

@ -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());
}
}

View file

@ -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),
;

View file

@ -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;

View file

@ -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;
}

View file

@ -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;

View file

@ -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)
;

View file

@ -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);

View file

@ -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;
}

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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);

View file

@ -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());
}

View file

@ -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();

View file

@ -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;
}

View file

@ -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);
}

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -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) {

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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);
}

View file

@ -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

View file

@ -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()),

View file

@ -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;

View file

@ -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);
}
}

View file

@ -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);

View file

@ -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.

View file

@ -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);
}
}
}

View file

@ -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 {

View file

@ -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);

View file

@ -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);
}

View file

@ -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) {

View file

@ -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

View file

@ -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>

View file

@ -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

View file

@ -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`))
;

View file

@ -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`))
;

View file

@ -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 */

View file

@ -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);
}

View file

@ -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>>() {

View file

@ -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>>() {

View file

@ -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`))
;